Skip to content

Commit 6f40376

Browse files
[SYCL] Fix dead pointer usage if leaf buffer overflows (#5417)
When leaf buffer overflows we take oldLeaf and make a new one dependent on it. In case of connect command insertion we again should take oldLeaf and do the same. So we do recursive work with the same buffer. This change helps to eliminate it by not adding connect command to the leaf buffer since we already put to the buffer or will put command depending on connectCmd. It also tries to remove duplicated dependencies between newLeaf and oldLeaf. If connection command inserted new leaf already depends on old leaf via connect cmd. So we do not put old leaf as standalone dependency. Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova@intel.com>
1 parent 5d4573f commit 6f40376

File tree

5 files changed

+202
-41
lines changed

5 files changed

+202
-41
lines changed

sycl/source/detail/scheduler/commands.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,15 @@ Command *Command::addDep(DepDesc NewDep, std::vector<Command *> &ToCleanUp) {
586586
ConnectionCmd =
587587
processDepEvent(NewDep.MDepCommand->getEvent(), NewDep, ToCleanUp);
588588
}
589-
MDeps.push_back(NewDep);
589+
// ConnectionCmd insertion builds the following dependency structure:
590+
// this -> emptyCmd (for ConnectionCmd) -> ConnectionCmd -> NewDep
591+
// that means that this and NewDep are already dependent
592+
if (!ConnectionCmd) {
593+
MDeps.push_back(NewDep);
594+
if (NewDep.MDepCommand)
595+
NewDep.MDepCommand->addUser(this);
596+
}
597+
590598
#ifdef XPTI_ENABLE_INSTRUMENTATION
591599
emitEdgeEventForCommandDependence(
592600
NewDep.MDepCommand, (void *)NewDep.MDepRequirement->MSYCLMemObj,

sycl/source/detail/scheduler/graph_builder.cpp

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ MemObjRecord *Scheduler::GraphBuilder::getOrInsertMemObjRecord(
190190
DepDesc Dep = findDepForRecord(Dependant, Record);
191191
Dep.MDepCommand = Dependency;
192192
std::vector<Command *> ToCleanUp;
193-
if (Command *ConnectionCmd = Dependant->addDep(Dep, ToCleanUp))
193+
Command *ConnectionCmd = Dependant->addDep(Dep, ToCleanUp);
194+
if (ConnectionCmd)
194195
ToEnqueue.push_back(ConnectionCmd);
195-
Dependency->addUser(Dependant);
196+
196197
--(Dependency->MLeafCounter);
197198
if (Dependency->MLeafCounter == 0 &&
198199
Dependency->isSuccessfullyEnqueued() &&
@@ -281,7 +282,6 @@ UpdateHostRequirementCommand *Scheduler::GraphBuilder::insertUpdateHostReqCmd(
281282
UpdateCommand->addDep(DepDesc{Dep, StoredReq, AllocaCmd}, ToCleanUp);
282283
if (ConnCmd)
283284
ToEnqueue.push_back(ConnCmd);
284-
Dep->addUser(UpdateCommand);
285285
}
286286
updateLeaves(Deps, Record, Req->MAccessMode, ToCleanUp);
287287
addNodeToLeaves(Record, UpdateCommand, Req->MAccessMode, ToEnqueue);
@@ -397,7 +397,6 @@ Command *Scheduler::GraphBuilder::insertMemoryMove(
397397
DepDesc{Dep, NewCmd->getRequirement(), AllocaCmdDst}, ToCleanUp);
398398
if (ConnCmd)
399399
ToEnqueue.push_back(ConnCmd);
400-
Dep->addUser(NewCmd);
401400
}
402401
updateLeaves(Deps, Record, access::mode::read_write, ToCleanUp);
403402
addNodeToLeaves(Record, NewCmd, access::mode::read_write, ToEnqueue);
@@ -437,14 +436,12 @@ Command *Scheduler::GraphBuilder::remapMemoryObject(
437436
DepDesc{Dep, UnMapCmd->getRequirement(), LinkedAllocaCmd}, ToCleanUp);
438437
if (ConnCmd)
439438
ToEnqueue.push_back(ConnCmd);
440-
Dep->addUser(UnMapCmd);
441439
}
442440

443441
Command *ConnCmd = MapCmd->addDep(
444442
DepDesc{UnMapCmd, MapCmd->getRequirement(), HostAllocaCmd}, ToCleanUp);
445443
if (ConnCmd)
446444
ToEnqueue.push_back(ConnCmd);
447-
UnMapCmd->addUser(MapCmd);
448445

449446
updateLeaves(Deps, Record, access::mode::read_write, ToCleanUp);
450447
addNodeToLeaves(Record, MapCmd, access::mode::read_write, ToEnqueue);
@@ -489,7 +486,6 @@ Scheduler::GraphBuilder::addCopyBack(Requirement *Req,
489486
DepDesc{Dep, MemCpyCmd->getRequirement(), SrcAllocaCmd}, ToCleanUp);
490487
if (ConnCmd)
491488
ToEnqueue.push_back(ConnCmd);
492-
Dep->addUser(MemCpyCmd);
493489
}
494490

495491
updateLeaves(Deps, Record, Req->MAccessMode, ToCleanUp);
@@ -780,7 +776,6 @@ AllocaCommandBase *Scheduler::GraphBuilder::getOrCreateAllocaForReq(
780776
ToCleanUp);
781777
if (ConnCmd)
782778
ToEnqueue.push_back(ConnCmd);
783-
LinkedAllocaCmd->addUser(AllocaCmd);
784779
LinkedAllocaCmd->MLinkedAllocaCmd = AllocaCmd;
785780

786781
// To ensure that the leader allocation is removed first
@@ -808,7 +803,6 @@ AllocaCommandBase *Scheduler::GraphBuilder::getOrCreateAllocaForReq(
808803
DepDesc{Dep, Req, LinkedAllocaCmd}, ToCleanUp);
809804
if (ConnCmd)
810805
ToEnqueue.push_back(ConnCmd);
811-
Dep->addUser(AllocaCmd);
812806
}
813807
updateLeaves(Deps, Record, Req->MAccessMode, ToCleanUp);
814808
addNodeToLeaves(Record, AllocaCmd, Req->MAccessMode, ToEnqueue);
@@ -848,7 +842,8 @@ typename detail::enable_if_t<
848842
Scheduler::GraphBuilder::addEmptyCmd(Command *Cmd, const std::vector<T *> &Reqs,
849843
const QueueImplPtr &Queue,
850844
Command::BlockReason Reason,
851-
std::vector<Command *> &ToEnqueue) {
845+
std::vector<Command *> &ToEnqueue,
846+
const bool AddDepsToLeaves) {
852847
EmptyCommand *EmptyCmd =
853848
new EmptyCommand(Scheduler::getInstance().getDefaultHostQueue());
854849

@@ -865,20 +860,24 @@ Scheduler::GraphBuilder::addEmptyCmd(Command *Cmd, const std::vector<T *> &Reqs,
865860
getOrCreateAllocaForReq(Record, Req, Queue, ToEnqueue);
866861
EmptyCmd->addRequirement(Cmd, AllocaCmd, Req);
867862
}
863+
// addRequirement above call addDep that already will add EmptyCmd as user for
864+
// Cmd no Reqs size check here so assume it is possible to have no Reqs passed
865+
if (!Reqs.size())
866+
Cmd->addUser(EmptyCmd);
868867

869-
Cmd->addUser(EmptyCmd);
870-
871-
const std::vector<DepDesc> &Deps = Cmd->MDeps;
872-
std::vector<Command *> ToCleanUp;
873-
for (const DepDesc &Dep : Deps) {
874-
const Requirement *Req = Dep.MDepRequirement;
875-
MemObjRecord *Record = getMemObjRecord(Req->MSYCLMemObj);
868+
if (AddDepsToLeaves) {
869+
const std::vector<DepDesc> &Deps = Cmd->MDeps;
870+
std::vector<Command *> ToCleanUp;
871+
for (const DepDesc &Dep : Deps) {
872+
const Requirement *Req = Dep.MDepRequirement;
873+
MemObjRecord *Record = getMemObjRecord(Req->MSYCLMemObj);
876874

877-
updateLeaves({Cmd}, Record, Req->MAccessMode, ToCleanUp);
878-
addNodeToLeaves(Record, EmptyCmd, Req->MAccessMode, ToEnqueue);
875+
updateLeaves({Cmd}, Record, Req->MAccessMode, ToCleanUp);
876+
addNodeToLeaves(Record, EmptyCmd, Req->MAccessMode, ToEnqueue);
877+
}
878+
for (Command *Cmd : ToCleanUp)
879+
cleanupCommand(Cmd);
879880
}
880-
for (Command *Cmd : ToCleanUp)
881-
cleanupCommand(Cmd);
882881

883882
return EmptyCmd;
884883
}
@@ -989,10 +988,12 @@ Scheduler::GraphBuilder::addCG(std::unique_ptr<detail::CG> CommandGroup,
989988
std::set<Command *> Deps =
990989
findDepsForReq(Record, Req, Queue->getContextImplPtr());
991990

992-
for (Command *Dep : Deps)
993-
if (Command *ConnCmd =
994-
NewCmd->addDep(DepDesc{Dep, Req, AllocaCmd}, ToCleanUp))
991+
for (Command *Dep : Deps) {
992+
Command *ConnCmd =
993+
NewCmd->addDep(DepDesc{Dep, Req, AllocaCmd}, ToCleanUp);
994+
if (ConnCmd)
995995
ToEnqueue.push_back(ConnCmd);
996+
}
996997
}
997998

998999
// Set new command as user for dependencies and update leaves.
@@ -1001,7 +1002,6 @@ Scheduler::GraphBuilder::addCG(std::unique_ptr<detail::CG> CommandGroup,
10011002
// FIXME employ a reference here to eliminate copying of a vector
10021003
std::vector<DepDesc> Deps = NewCmd->MDeps;
10031004
for (DepDesc &Dep : Deps) {
1004-
Dep.MDepCommand->addUser(NewCmd.get());
10051005
const Requirement *Req = Dep.MDepRequirement;
10061006
MemObjRecord *Record = getMemObjRecord(Req->MSYCLMemObj);
10071007
updateLeaves({Dep.MDepCommand}, Record, Req->MAccessMode, ToCleanUp);
@@ -1297,9 +1297,6 @@ Command *Scheduler::GraphBuilder::connectDepEvent(
12971297
throw runtime_error("Out of host memory", PI_OUT_OF_HOST_MEMORY);
12981298
}
12991299

1300-
if (Command *DepCmd = reinterpret_cast<Command *>(DepEvent->getCommand()))
1301-
DepCmd->addUser(ConnectCmd);
1302-
13031300
EmptyCommand *EmptyCmd = nullptr;
13041301

13051302
if (Dep.MDepRequirement) {
@@ -1311,21 +1308,17 @@ Command *Scheduler::GraphBuilder::connectDepEvent(
13111308
Dep.MDepCommand);
13121309
// add user to Dep.MDepCommand is already performed beyond this if branch
13131310

1314-
MemObjRecord *Record = getMemObjRecord(Dep.MDepRequirement->MSYCLMemObj);
1315-
updateLeaves({Dep.MDepCommand}, Record, Dep.MDepRequirement->MAccessMode,
1316-
ToCleanUp);
1311+
// ConnectCmd is added as dependency to Cmd
1312+
// We build the following structure Cmd->EmptyCmd/ConnectCmd->DepCmd
1313+
// No need to add ConnectCmd to leaves buffer since it is a dependency
1314+
// for command Cmd that will be added there
13171315

13181316
std::vector<Command *> ToEnqueue;
1319-
addNodeToLeaves(Record, ConnectCmd, Dep.MDepRequirement->MAccessMode,
1320-
ToEnqueue);
1321-
assert(ToEnqueue.size() == 0);
1322-
13231317
const std::vector<const Requirement *> Reqs(1, Dep.MDepRequirement);
13241318
EmptyCmd = addEmptyCmd(ConnectCmd, Reqs,
13251319
Scheduler::getInstance().getDefaultHostQueue(),
1326-
Command::BlockReason::HostTask, ToEnqueue);
1320+
Command::BlockReason::HostTask, ToEnqueue, false);
13271321
assert(ToEnqueue.size() == 0);
1328-
// Dependencies for EmptyCmd are set in addEmptyCmd for provided Reqs.
13291322

13301323
// Depend Cmd on empty command
13311324
{
@@ -1337,6 +1330,11 @@ Command *Scheduler::GraphBuilder::connectDepEvent(
13371330
(void)Cmd->addDep(CmdDep, ToCleanUp);
13381331
}
13391332
} else {
1333+
// It is required condition in another a path and addUser will be set in
1334+
// addDep
1335+
if (Command *DepCmd = reinterpret_cast<Command *>(DepEvent->getCommand()))
1336+
DepCmd->addUser(ConnectCmd);
1337+
13401338
std::vector<Command *> ToEnqueue;
13411339
EmptyCmd = addEmptyCmd<Requirement>(
13421340
ConnectCmd, {}, Scheduler::getInstance().getDefaultHostQueue(),
@@ -1354,10 +1352,10 @@ Command *Scheduler::GraphBuilder::connectDepEvent(
13541352
// Dismiss the result here as it's not a connection now,
13551353
// 'cause EmptyCmd is host one
13561354
(void)Cmd->addDep(EmptyCmd->getEvent(), ToCleanUp);
1355+
// Added by addDep in another path
1356+
EmptyCmd->addUser(Cmd);
13571357
}
13581358

1359-
EmptyCmd->addUser(Cmd);
1360-
13611359
ConnectCmd->MEmptyCmd = EmptyCmd;
13621360

13631361
return ConnectCmd;

sycl/source/detail/scheduler/scheduler.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,8 @@ class Scheduler {
606606
EmptyCommand *>
607607
addEmptyCmd(Command *Cmd, const std::vector<T *> &Req,
608608
const QueueImplPtr &Queue, Command::BlockReason Reason,
609-
std::vector<Command *> &ToEnqueue);
609+
std::vector<Command *> &ToEnqueue,
610+
const bool AddDepsToLeaves = true);
610611

611612
protected:
612613
/// Finds a command dependency corresponding to the record.

sycl/unittests/scheduler/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ add_sycl_unittest(SchedulerTests OBJECT
1818
QueueFlushing.cpp
1919
PostEnqueueCleanup.cpp
2020
utils.cpp
21+
LeafLimitDiffContexts.cpp
2122
)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//==---------------- LeafLimit.cpp --- Scheduler unit tests ----------------==//
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 "SchedulerTest.hpp"
10+
#include "SchedulerTestUtils.hpp"
11+
12+
#include <detail/config.hpp>
13+
#include <helpers/ScopedEnvVar.hpp>
14+
15+
#include <algorithm>
16+
#include <cstddef>
17+
#include <memory>
18+
#include <vector>
19+
20+
using namespace cl::sycl;
21+
22+
inline constexpr auto DisablePostEnqueueCleanupName =
23+
"SYCL_DISABLE_POST_ENQUEUE_CLEANUP";
24+
25+
// Checks that scheduler's (or graph-builder's) addNodeToLeaves method works
26+
// correctly with dependency tracking when leaf-limit for generic commands is
27+
// overflowed.
28+
// Checks that in case of different contexts for deleted leaf and a new one
29+
// ConnectCmd will be created and scheduler will build the following dependency
30+
// structure: NewLeaf->EmptyCmd/ConnectCmd->OldLeaf
31+
TEST_F(SchedulerTest, LeafLimitDiffContexts) {
32+
// All of the mock commands are owned on the test side, prevent post enqueue
33+
// cleanup from deleting some of them.
34+
unittest::ScopedEnvVar DisabledCleanup{
35+
DisablePostEnqueueCleanupName, "1",
36+
detail::SYCLConfig<detail::SYCL_DISABLE_POST_ENQUEUE_CLEANUP>::reset};
37+
38+
default_selector Selector;
39+
device Device = Selector.select_device();
40+
// ConnectCmd will not be created for host contextx
41+
if (Device.is_host()) {
42+
std::cout << "Not run due to host-only environment\n";
43+
return;
44+
}
45+
46+
struct QueueRelatedObjects {
47+
context Context;
48+
queue Queue;
49+
std::unique_ptr<MockCommand> DepCmd;
50+
detail::MemObjRecord *Rec;
51+
detail::AllocaCommandBase *AllocaCmd;
52+
53+
QueueRelatedObjects(const device &Dev)
54+
: Context(Dev), Queue(Context, Dev), DepCmd(), Rec(nullptr),
55+
AllocaCmd(nullptr) {}
56+
57+
void InitializeUtils(detail::Requirement &MockReq, MockScheduler &MS) {
58+
std::vector<detail::Command *> ToEnqueue;
59+
Rec = MS.getOrInsertMemObjRecord(detail::getSyclObjImpl(Queue), &MockReq,
60+
ToEnqueue);
61+
// Creating Alloca on both - device and host contexts (will be created in
62+
// real case in insertMemMove for example) It is done to avoid extra
63+
// AllocCmd insertion during ConnectCmd insertion
64+
AllocaCmd = MS.getOrCreateAllocaForReq(
65+
Rec, &MockReq, detail::getSyclObjImpl(Queue), ToEnqueue);
66+
std::ignore = MS.getOrCreateAllocaForReq(
67+
Rec, &MockReq, MS.getDefaultHostQueue(), ToEnqueue);
68+
DepCmd =
69+
std::make_unique<MockCommand>(detail::getSyclObjImpl(Queue), MockReq);
70+
}
71+
};
72+
73+
// Creating 2 queues with different context objects
74+
QueueRelatedObjects ExtQueue1(Device), ExtQueue2(Device);
75+
MockScheduler MS;
76+
std::vector<std::unique_ptr<MockCommand>> AddedLeaves;
77+
78+
buffer<int, 1> Buf(range<1>(1));
79+
detail::Requirement MockReq = getMockRequirement(Buf);
80+
81+
ExtQueue1.InitializeUtils(MockReq, MS);
82+
ExtQueue2.InitializeUtils(MockReq, MS);
83+
84+
size_t CommandsCapacity =
85+
ExtQueue1.Rec->MWriteLeaves.genericCommandsCapacity();
86+
87+
// Adds leaf with 1 deps to buffer
88+
auto AddLeafWithDeps = [&AddedLeaves, &MockReq,
89+
&MS](const QueueRelatedObjects &QueueStuff) {
90+
auto NewLeaf = std::make_unique<MockCommand>(
91+
detail::getSyclObjImpl(QueueStuff.Queue), MockReq);
92+
// Create edges: all soon-to-be leaves are direct users of MockDep
93+
std::vector<detail::Command *> ToCleanUp;
94+
(void)NewLeaf->addDep(detail::DepDesc{QueueStuff.DepCmd.get(), &MockReq,
95+
QueueStuff.AllocaCmd},
96+
ToCleanUp);
97+
QueueStuff.DepCmd->addUser(NewLeaf.get());
98+
99+
std::vector<detail::Command *> ToEnqueue;
100+
MS.addNodeToLeaves(QueueStuff.Rec, NewLeaf.get(), access::mode::write,
101+
ToEnqueue);
102+
AddedLeaves.push_back(std::move(NewLeaf));
103+
};
104+
105+
// Create commands that will be added as leaves up to the limit
106+
for (std::size_t i = 0; i < CommandsCapacity; ++i) {
107+
AddLeafWithDeps(ExtQueue1);
108+
}
109+
// Adding extra command on different to exceed buffer limit
110+
// The command #0 and command #8 must be on different queues to insert connect
111+
// command
112+
AddLeafWithDeps(ExtQueue2);
113+
114+
// Check that the oldest leaf #0 has been removed from the leaf list
115+
const detail::CircularBuffer<detail::Command *> &Leaves =
116+
ExtQueue1.Rec->MWriteLeaves.getGenericCommands();
117+
ASSERT_TRUE(std::find(Leaves.begin(), Leaves.end(),
118+
AddedLeaves.front().get()) == Leaves.end());
119+
// Check that another leaves #1...#7 that should not be removed are present in
120+
// buffer
121+
for (std::size_t i = 1; i < AddedLeaves.size(); ++i) {
122+
assert(std::find(Leaves.begin(), Leaves.end(), AddedLeaves[i].get()) !=
123+
Leaves.end());
124+
}
125+
126+
// Check NewLeaf->EmptyCmd/ConnectCmd->OldLeaf structure
127+
MockCommand *OldestLeaf = AddedLeaves.front().get();
128+
MockCommand *NewestLeaf = AddedLeaves.back().get();
129+
// The only user for oldLeaf must be ConnectCmd
130+
ASSERT_EQ(OldestLeaf->MUsers.size(), 1U);
131+
// No direct connection between OldLeaf and newLeaf, only via ConnectCmd
132+
EXPECT_EQ(OldestLeaf->MUsers.count(NewestLeaf), 0U);
133+
// 2 deps for NewLeaf: 1 dep command and connect cmd - no OldLeaf direct
134+
// dependency
135+
ASSERT_EQ(NewestLeaf->MDeps.size(), 2U);
136+
EXPECT_FALSE(std::any_of(
137+
NewestLeaf->MDeps.begin(), NewestLeaf->MDeps.end(),
138+
[&](const detail::DepDesc &DD) { return DD.MDepCommand == OldestLeaf; }));
139+
// Check NewLeaf dependencies in depth by MUsers
140+
auto ConnectCmdIt = OldestLeaf->MUsers.begin();
141+
ASSERT_EQ((*ConnectCmdIt)->MUsers.size(), 1U);
142+
auto EmptyCmdIt = (*ConnectCmdIt)->MUsers.begin();
143+
EXPECT_TRUE(std::any_of(NewestLeaf->MDeps.begin(), NewestLeaf->MDeps.end(),
144+
[&](const detail::DepDesc &DD) {
145+
return DD.MDepCommand == (*EmptyCmdIt);
146+
}));
147+
// ConnectCmd is created internally in scheduler and not a mock object
148+
// This fact leads to active scheduler shutdown process that deletes a
149+
// part of commands for record we store in AddedLeaves vector.
150+
// We abort this process by removing record to avoid double release or
151+
// or memory leaks
152+
MS.removeRecordForMemObj(MockReq.MSYCLMemObj);
153+
}

0 commit comments

Comments
 (0)