Skip to content

Commit 1880d8f

Browse files
committed
[OpenMP][Archer] Add support for taskwait depend
At the moment Archer segfaults due to a null-pointer access, if an application uses taskwait with depend clause as used in the two new tests. This patch cleans up the task_schedule function, moves semantic blocks into functions and replaces the if blocks by a single switch statement. The switch statement will warn, when new enum values are added in OMPT and makes clear what code is executed for the different cases. With free-agent tasks coming up in OpenMP 6.0, we should expect more null-pointer task_data, so additional null-pointer checks were added. We also cannot rely on having an implicit task on the stack, so the BarrierIndex is stored during task creation. Differential Revision: https://reviews.llvm.org/D158072
1 parent ab090e9 commit 1880d8f

File tree

3 files changed

+245
-79
lines changed

3 files changed

+245
-79
lines changed

openmp/tools/archer/ompt-tsan.cpp

Lines changed: 129 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ struct Taskgroup final : DataPoolEntry<Taskgroup> {
444444
Taskgroup(DataPool<Taskgroup> *dp) : DataPoolEntry<Taskgroup>(dp) {}
445445
};
446446

447+
enum ArcherTaskFlag { ArcherTaskFulfilled = 0x00010000 };
448+
447449
struct TaskData;
448450
typedef DataPool<TaskData> TaskDataPool;
449451
template <> __thread TaskDataPool *TaskDataPool::ThreadDataPool = nullptr;
@@ -460,6 +462,9 @@ struct TaskData final : DataPoolEntry<TaskData> {
460462
/// Child tasks use its address to model omp_all_memory dependencies
461463
ompt_tsan_clockid AllMemory[2]{0};
462464

465+
/// Index of which barrier to use next.
466+
char BarrierIndex{0};
467+
463468
/// Whether this task is currently executing a barrier.
464469
bool InBarrier{false};
465470

@@ -469,18 +474,12 @@ struct TaskData final : DataPoolEntry<TaskData> {
469474
/// count execution phase
470475
int execution{0};
471476

472-
/// Index of which barrier to use next.
473-
char BarrierIndex{0};
474-
475477
/// Count how often this structure has been put into child tasks + 1.
476478
std::atomic_int RefCount{1};
477479

478480
/// Reference to the parent that created this task.
479481
TaskData *Parent{nullptr};
480482

481-
/// Reference to the implicit task in the stack above this task.
482-
TaskData *ImplicitTask{nullptr};
483-
484483
/// Reference to the team of this task.
485484
ParallelData *Team{nullptr};
486485

@@ -515,6 +514,9 @@ struct TaskData final : DataPoolEntry<TaskData> {
515514
bool isInitial() { return TaskType & ompt_task_initial; }
516515
bool isTarget() { return TaskType & ompt_task_target; }
517516

517+
bool isFulfilled() { return TaskType & ArcherTaskFulfilled; }
518+
void setFulfilled() { TaskType |= ArcherTaskFulfilled; }
519+
518520
void setAllMemoryDep() { AllMemory[0] = 1; }
519521
bool hasAllMemoryDep() { return AllMemory[0]; }
520522

@@ -529,6 +531,7 @@ struct TaskData final : DataPoolEntry<TaskData> {
529531
TaskType = taskType;
530532
Parent = parent;
531533
Team = Parent->Team;
534+
BarrierIndex = Parent->BarrierIndex;
532535
if (Parent != nullptr) {
533536
Parent->RefCount++;
534537
// Copy over pointer to taskgroup. This task may set up its own stack
@@ -541,7 +544,6 @@ struct TaskData final : DataPoolEntry<TaskData> {
541544
TaskData *Init(ParallelData *team, int taskType) {
542545
TaskType = taskType;
543546
execution = 1;
544-
ImplicitTask = this;
545547
Team = team;
546548
return this;
547549
}
@@ -553,7 +555,6 @@ struct TaskData final : DataPoolEntry<TaskData> {
553555
BarrierIndex = 0;
554556
RefCount = 1;
555557
Parent = nullptr;
556-
ImplicitTask = nullptr;
557558
Team = nullptr;
558559
TaskGroup = nullptr;
559560
if (DependencyMap) {
@@ -584,7 +585,9 @@ struct TaskData final : DataPoolEntry<TaskData> {
584585
} // namespace
585586

586587
static inline TaskData *ToTaskData(ompt_data_t *task_data) {
587-
return reinterpret_cast<TaskData *>(task_data->ptr);
588+
if (task_data)
589+
return reinterpret_cast<TaskData *>(task_data->ptr);
590+
return nullptr;
588591
}
589592

590593
/// Store a mutex for each wait_id to resolve race condition with callbacks.
@@ -899,6 +902,79 @@ static void acquireDependencies(TaskData *task) {
899902
}
900903
}
901904

905+
static void completeTask(TaskData *FromTask) {
906+
if (!FromTask)
907+
return;
908+
// Task-end happens after a possible omp_fulfill_event call
909+
if (FromTask->isFulfilled())
910+
TsanHappensAfter(FromTask->GetTaskPtr());
911+
// Included tasks are executed sequentially, no need to track
912+
// synchronization
913+
if (!FromTask->isIncluded()) {
914+
// Task will finish before a barrier in the surrounding parallel region
915+
// ...
916+
ParallelData *PData = FromTask->Team;
917+
TsanHappensBefore(PData->GetBarrierPtr(FromTask->BarrierIndex));
918+
919+
// ... and before an eventual taskwait by the parent thread.
920+
TsanHappensBefore(FromTask->Parent->GetTaskwaitPtr());
921+
922+
if (FromTask->TaskGroup != nullptr) {
923+
// This task is part of a taskgroup, so it will finish before the
924+
// corresponding taskgroup_end.
925+
TsanHappensBefore(FromTask->TaskGroup->GetPtr());
926+
}
927+
}
928+
// release dependencies
929+
releaseDependencies(FromTask);
930+
}
931+
932+
static void suspendTask(TaskData *FromTask) {
933+
if (!FromTask)
934+
return;
935+
// Task may be resumed at a later point in time.
936+
TsanHappensBefore(FromTask->GetTaskPtr());
937+
}
938+
939+
static void switchTasks(TaskData *FromTask, TaskData *ToTask) {
940+
// Legacy handling for missing reduction callback
941+
if (hasReductionCallback < ompt_set_always) {
942+
if (FromTask && FromTask->InBarrier) {
943+
// We want to ignore writes in the runtime code during barriers,
944+
// but not when executing tasks with user code!
945+
TsanIgnoreWritesEnd();
946+
}
947+
if (ToTask && ToTask->InBarrier) {
948+
// We want to ignore writes in the runtime code during barriers,
949+
// but not when executing tasks with user code!
950+
TsanIgnoreWritesBegin();
951+
}
952+
}
953+
//// Not yet used
954+
// if (FromTask)
955+
// FromTask->deactivate();
956+
// if (ToTask)
957+
// ToTask->activate();
958+
}
959+
960+
static void endTask(TaskData *FromTask) {
961+
if (!FromTask)
962+
return;
963+
}
964+
965+
static void startTask(TaskData *ToTask) {
966+
if (!ToTask)
967+
return;
968+
// Handle dependencies on first execution of the task
969+
if (ToTask->execution == 0) {
970+
ToTask->execution++;
971+
acquireDependencies(ToTask);
972+
}
973+
// 1. Task will begin execution after it has been created.
974+
// 2. Task will resume after it has been switched away.
975+
TsanHappensAfter(ToTask->GetTaskPtr());
976+
}
977+
902978
static void ompt_tsan_task_schedule(ompt_data_t *first_task_data,
903979
ompt_task_status_t prior_task_status,
904980
ompt_data_t *second_task_data) {
@@ -916,88 +992,62 @@ static void ompt_tsan_task_schedule(ompt_data_t *first_task_data,
916992
// ompt_task_cancel = 3,
917993
// -> first completed, first freed, second starts
918994
//
995+
// ompt_taskwait_complete = 8,
996+
// -> first starts, first completes, first freed, second ignored
997+
//
919998
// ompt_task_detach = 4,
920999
// ompt_task_yield = 2,
9211000
// ompt_task_switch = 7
9221001
// -> first suspended, second starts
9231002
//
9241003

925-
if (prior_task_status == ompt_task_early_fulfill)
926-
return;
927-
9281004
TaskData *FromTask = ToTaskData(first_task_data);
1005+
TaskData *ToTask = ToTaskData(second_task_data);
9291006

930-
// Legacy handling for missing reduction callback
931-
if (hasReductionCallback < ompt_set_always && FromTask->InBarrier) {
932-
// We want to ignore writes in the runtime code during barriers,
933-
// but not when executing tasks with user code!
934-
TsanIgnoreWritesEnd();
935-
}
936-
937-
// The late fulfill happens after the detached task finished execution
938-
if (prior_task_status == ompt_task_late_fulfill)
1007+
switch (prior_task_status) {
1008+
case ompt_task_early_fulfill:
1009+
TsanHappensBefore(FromTask->GetTaskPtr());
1010+
FromTask->setFulfilled();
1011+
return;
1012+
case ompt_task_late_fulfill:
9391013
TsanHappensAfter(FromTask->GetTaskPtr());
940-
941-
// task completed execution
942-
if (prior_task_status == ompt_task_complete ||
943-
prior_task_status == ompt_task_cancel ||
944-
prior_task_status == ompt_task_late_fulfill) {
945-
// Included tasks are executed sequentially, no need to track
946-
// synchronization
947-
if (!FromTask->isIncluded()) {
948-
// Task will finish before a barrier in the surrounding parallel region
949-
// ...
950-
ParallelData *PData = FromTask->Team;
951-
TsanHappensBefore(
952-
PData->GetBarrierPtr(FromTask->ImplicitTask->BarrierIndex));
953-
954-
// ... and before an eventual taskwait by the parent thread.
955-
TsanHappensBefore(FromTask->Parent->GetTaskwaitPtr());
956-
957-
if (FromTask->TaskGroup != nullptr) {
958-
// This task is part of a taskgroup, so it will finish before the
959-
// corresponding taskgroup_end.
960-
TsanHappensBefore(FromTask->TaskGroup->GetPtr());
961-
}
962-
}
963-
964-
// release dependencies
965-
releaseDependencies(FromTask);
966-
// free the previously running task
1014+
completeTask(FromTask);
9671015
freeTask(FromTask);
968-
}
969-
970-
// For late fulfill of detached task, there is no task to schedule to
971-
if (prior_task_status == ompt_task_late_fulfill) {
1016+
return;
1017+
case ompt_taskwait_complete:
1018+
acquireDependencies(FromTask);
1019+
freeTask(FromTask);
1020+
return;
1021+
case ompt_task_complete:
1022+
completeTask(FromTask);
1023+
endTask(FromTask);
1024+
switchTasks(FromTask, ToTask);
1025+
freeTask(FromTask);
1026+
return;
1027+
case ompt_task_cancel:
1028+
completeTask(FromTask);
1029+
endTask(FromTask);
1030+
switchTasks(FromTask, ToTask);
1031+
freeTask(FromTask);
1032+
startTask(ToTask);
1033+
return;
1034+
case ompt_task_detach:
1035+
endTask(FromTask);
1036+
suspendTask(FromTask);
1037+
switchTasks(FromTask, ToTask);
1038+
startTask(ToTask);
1039+
return;
1040+
case ompt_task_yield:
1041+
suspendTask(FromTask);
1042+
switchTasks(FromTask, ToTask);
1043+
startTask(ToTask);
1044+
return;
1045+
case ompt_task_switch:
1046+
suspendTask(FromTask);
1047+
switchTasks(FromTask, ToTask);
1048+
startTask(ToTask);
9721049
return;
9731050
}
974-
975-
TaskData *ToTask = ToTaskData(second_task_data);
976-
// Legacy handling for missing reduction callback
977-
if (hasReductionCallback < ompt_set_always && ToTask->InBarrier) {
978-
// We re-enter runtime code which currently performs a barrier.
979-
TsanIgnoreWritesBegin();
980-
}
981-
982-
// task suspended
983-
if (prior_task_status == ompt_task_switch ||
984-
prior_task_status == ompt_task_yield ||
985-
prior_task_status == ompt_task_detach) {
986-
// Task may be resumed at a later point in time.
987-
TsanHappensBefore(FromTask->GetTaskPtr());
988-
ToTask->ImplicitTask = FromTask->ImplicitTask;
989-
assert(ToTask->ImplicitTask != NULL &&
990-
"A task belongs to a team and has an implicit task on the stack");
991-
}
992-
993-
// Handle dependencies on first execution of the task
994-
if (ToTask->execution == 0) {
995-
ToTask->execution++;
996-
acquireDependencies(ToTask);
997-
}
998-
// 1. Task will begin execution after it has been created.
999-
// 2. Task will resume after it has been switched away.
1000-
TsanHappensAfter(ToTask->GetTaskPtr());
10011051
}
10021052

10031053
static void ompt_tsan_dependences(ompt_data_t *task_data,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* taskwait-depend.c -- Archer testcase
3+
* derived from DRB165-taskdep4-orig-omp50-yes.c in DataRaceBench
4+
*/
5+
//===----------------------------------------------------------------------===//
6+
//
7+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
8+
//
9+
// See tools/archer/LICENSE.txt for details.
10+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
// RUN: %libarcher-compile-and-run-race | FileCheck %s
15+
// RUN: %libarcher-compile-and-run-race-noserial | FileCheck %s
16+
// REQUIRES: tsan
17+
18+
#include "ompt/ompt-signal.h"
19+
#include <omp.h>
20+
#include <stdio.h>
21+
22+
void foo() {
23+
24+
int x = 0, y = 2, sem = 0;
25+
26+
#pragma omp task depend(inout : x) shared(x, sem)
27+
{
28+
OMPT_SIGNAL(sem);
29+
x++; // 1st Child Task
30+
}
31+
32+
#pragma omp task shared(y, sem)
33+
{
34+
OMPT_SIGNAL(sem);
35+
y--; // 2nd child task
36+
}
37+
38+
OMPT_WAIT(sem, 2);
39+
#pragma omp taskwait depend(in : x) // 1st taskwait
40+
41+
printf("x=%d\n", x);
42+
printf("y=%d\n", y);
43+
#pragma omp taskwait // 2nd taskwait
44+
}
45+
46+
int main() {
47+
#pragma omp parallel num_threads(2)
48+
#pragma omp single
49+
foo();
50+
51+
return 0;
52+
}
53+
54+
// CHECK: WARNING: ThreadSanitizer: data race
55+
// CHECK-NEXT: {{(Write|Read)}} of size 4
56+
// CHECK-NEXT: #0 {{.*}}taskwait-depend.c:42:20
57+
// CHECK: Previous write of size 4
58+
// CHECK-NEXT: #0 {{.*}}taskwait-depend.c:35:6
59+
// CHECK: ThreadSanitizer: reported {{[0-9]+}} warnings

0 commit comments

Comments
 (0)