-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Implementation of task_group dynamic dependencies - part 1 - task_completion_handle #1682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ef1b4a9
fbc4e51
31caf83
25b7bea
3297d82
9f1d0e8
d6a612d
d821614
c9d0bee
9890220
a2f1b8f
9b90931
915d864
f3c41e9
c81d436
e518ea7
2c79633
e1da595
c3fbb6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
/* | ||
Copyright (c) 2020-2024 Intel Corporation | ||
Copyright (c) 2020-2025 Intel Corporation | ||
Copyright (c) 2025 UXL Foundation Contributors | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
|
@@ -32,11 +33,39 @@ namespace d2 { | |
|
||
class task_handle; | ||
|
||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
|
||
class task_dynamic_state { | ||
public: | ||
task_dynamic_state(d1::small_object_allocator& alloc) | ||
: m_num_references(1) // reserves a task co-ownership for dynamic state | ||
, m_allocator(alloc) | ||
{} | ||
|
||
void reserve() { ++m_num_references; } | ||
|
||
void release() { | ||
if (--m_num_references == 0) { | ||
m_allocator.delete_object(this); | ||
} | ||
} | ||
|
||
void complete_task() { | ||
} | ||
private: | ||
std::atomic<std::size_t> m_num_references; | ||
d1::small_object_allocator m_allocator; | ||
}; | ||
#endif // __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
|
||
class task_handle_task : public d1::task { | ||
std::uint64_t m_version_and_traits{}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have just checked and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the scheduler a different one is used, I believe:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two m_version_and_traits, one in task_traits, accessible via task_accessor, and another one in task_handle_task. |
||
d1::wait_tree_vertex_interface* m_wait_tree_vertex; | ||
d1::task_group_context& m_ctx; | ||
d1::small_object_allocator m_allocator; | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
std::atomic<task_dynamic_state*> m_dynamic_state; | ||
#endif | ||
public: | ||
void finalize(const d1::execution_data* ed = nullptr) { | ||
if (ed) { | ||
|
@@ -49,16 +78,56 @@ class task_handle_task : public d1::task { | |
task_handle_task(d1::wait_tree_vertex_interface* vertex, d1::task_group_context& ctx, d1::small_object_allocator& alloc) | ||
: m_wait_tree_vertex(vertex) | ||
, m_ctx(ctx) | ||
, m_allocator(alloc) { | ||
, m_allocator(alloc) | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
, m_dynamic_state(nullptr) | ||
#endif | ||
{ | ||
suppress_unused_warning(m_version_and_traits); | ||
m_wait_tree_vertex->reserve(); | ||
} | ||
|
||
~task_handle_task() override { | ||
m_wait_tree_vertex->release(); | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
task_dynamic_state* current_state = m_dynamic_state.load(std::memory_order_relaxed); | ||
if (current_state != nullptr) { | ||
current_state->release(); | ||
} | ||
#endif | ||
} | ||
|
||
d1::task_group_context& ctx() const { return m_ctx; } | ||
|
||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
// Returns the dynamic state associated with the task. If the state has not been initialized, initializes it. | ||
task_dynamic_state* get_dynamic_state() { | ||
task_dynamic_state* current_state = m_dynamic_state.load(std::memory_order_acquire); | ||
|
||
if (current_state == nullptr) { | ||
d1::small_object_allocator alloc; | ||
|
||
task_dynamic_state* new_state = alloc.new_object<task_dynamic_state>(alloc); | ||
Comment on lines
+108
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we cannot reuse it since the |
||
|
||
if (m_dynamic_state.compare_exchange_strong(current_state, new_state)) { | ||
current_state = new_state; | ||
} else { | ||
// CAS failed, current_state points to the dynamic state created by another thread | ||
alloc.delete_object(new_state); | ||
} | ||
} | ||
|
||
__TBB_ASSERT(current_state != nullptr, "Failed to create dynamic state"); | ||
return current_state; | ||
} | ||
|
||
void complete_task() { | ||
task_dynamic_state* current_state = m_dynamic_state.load(std::memory_order_relaxed); | ||
if (current_state != nullptr) { | ||
current_state->complete_task(); | ||
} | ||
} | ||
#endif | ||
}; | ||
|
||
|
||
|
@@ -84,21 +153,26 @@ class task_handle { | |
|
||
private: | ||
friend struct task_handle_accessor; | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
friend class task_completion_handle; | ||
#endif | ||
|
||
task_handle(task_handle_task* t) : m_handle {t}{}; | ||
task_handle(task_handle_task* t) : m_handle {t}{} | ||
|
||
d1::task* release() { | ||
return m_handle.release(); | ||
} | ||
}; | ||
|
||
struct task_handle_accessor { | ||
static task_handle construct(task_handle_task* t) { return {t}; } | ||
static d1::task* release(task_handle& th) { return th.release(); } | ||
static d1::task_group_context& ctx_of(task_handle& th) { | ||
__TBB_ASSERT(th.m_handle, "ctx_of does not expect empty task_handle."); | ||
return th.m_handle->ctx(); | ||
} | ||
static task_handle construct(task_handle_task* t) { return {t}; } | ||
|
||
static d1::task* release(task_handle& th) { return th.release(); } | ||
|
||
static d1::task_group_context& ctx_of(task_handle& th) { | ||
__TBB_ASSERT(th.m_handle, "ctx_of does not expect empty task_handle."); | ||
return th.m_handle->ctx(); | ||
} | ||
}; | ||
|
||
inline bool operator==(task_handle const& th, std::nullptr_t) noexcept { | ||
|
@@ -116,6 +190,108 @@ inline bool operator!=(std::nullptr_t, task_handle const& th) noexcept { | |
return th.m_handle != nullptr; | ||
} | ||
|
||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
class task_completion_handle { | ||
public: | ||
task_completion_handle() : m_task_state(nullptr) {} | ||
|
||
task_completion_handle(const task_completion_handle& other) | ||
: m_task_state(other.m_task_state) | ||
{ | ||
// Register one more co-owner of the dynamic state | ||
if (m_task_state) m_task_state->reserve(); | ||
} | ||
task_completion_handle(task_completion_handle&& other) | ||
: m_task_state(other.m_task_state) | ||
{ | ||
other.m_task_state = nullptr; | ||
} | ||
|
||
task_completion_handle(const task_handle& th) | ||
: m_task_state(nullptr) | ||
{ | ||
__TBB_ASSERT(th, "Construction of task_completion_handle from an empty task_handle"); | ||
m_task_state = th.m_handle->get_dynamic_state(); | ||
// Register one more co-owner of the dynamic state | ||
m_task_state->reserve(); | ||
} | ||
|
||
~task_completion_handle() { | ||
if (m_task_state) m_task_state->release(); | ||
} | ||
|
||
task_completion_handle& operator=(const task_completion_handle& other) { | ||
if (m_task_state != other.m_task_state) { | ||
// Release co-ownership on the previously tracked dynamic state | ||
if (m_task_state) m_task_state->release(); | ||
|
||
m_task_state = other.m_task_state; | ||
|
||
// Register new co-owner of the new dynamic state | ||
if (m_task_state) m_task_state->reserve(); | ||
} | ||
return *this; | ||
} | ||
|
||
task_completion_handle& operator=(task_completion_handle&& other) { | ||
if (this != &other) { | ||
// Release co-ownership on the previously tracked dynamic state | ||
if (m_task_state) m_task_state->release(); | ||
|
||
m_task_state = other.m_task_state; | ||
other.m_task_state = nullptr; | ||
} | ||
return *this; | ||
} | ||
|
||
task_completion_handle& operator=(const task_handle& th) { | ||
__TBB_ASSERT(th, "Assignment of task_completion_state from an empty task_handle"); | ||
task_dynamic_state* th_state = th.m_handle->get_dynamic_state(); | ||
__TBB_ASSERT(th_state != nullptr, "No state in the non-empty task_handle"); | ||
if (m_task_state != th_state) { | ||
// Release co-ownership on the previously tracked dynamic state | ||
if (m_task_state) m_task_state->release(); | ||
|
||
m_task_state = th_state; | ||
|
||
// Reserve co-ownership on the new dynamic state | ||
m_task_state->reserve(); | ||
} | ||
return *this; | ||
} | ||
|
||
explicit operator bool() const noexcept { return m_task_state != nullptr; } | ||
private: | ||
friend bool operator==(const task_completion_handle& t, std::nullptr_t) noexcept { | ||
return t.m_task_state == nullptr; | ||
} | ||
|
||
friend bool operator==(const task_completion_handle& lhs, const task_completion_handle& rhs) noexcept { | ||
return lhs.m_task_state == rhs.m_task_state; | ||
} | ||
|
||
#if !__TBB_CPP20_COMPARISONS_PRESENT | ||
friend bool operator==(std::nullptr_t, const task_completion_handle& t) noexcept { | ||
return t == nullptr; | ||
} | ||
|
||
friend bool operator!=(const task_completion_handle& t, std::nullptr_t) noexcept { | ||
return !(t == nullptr); | ||
} | ||
|
||
friend bool operator!=(std::nullptr_t, const task_completion_handle& t) noexcept { | ||
return !(t == nullptr); | ||
} | ||
|
||
friend bool operator!=(const task_completion_handle& lhs, const task_completion_handle& rhs) noexcept { | ||
return !(lhs == rhs); | ||
} | ||
#endif // !__TBB_CPP20_COMPARISONS_PRESENT | ||
|
||
task_dynamic_state* m_task_state; | ||
}; | ||
#endif | ||
|
||
} // namespace d2 | ||
} // namespace detail | ||
} // namespace tbb | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
/* | ||
Copyright (c) 2005-2024 Intel Corporation | ||
Copyright (c) 2005-2025 Intel Corporation | ||
Copyright (c) 2025 UXL Foundation Contributors | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
|
@@ -87,6 +88,9 @@ class function_task : public task_handle_task { | |
d1::task* execute(d1::execution_data& ed) override { | ||
__TBB_ASSERT(ed.context == &this->ctx(), "The task group context should be used for all tasks"); | ||
task* res = task_ptr_or_nullptr(m_func); | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
this->complete_task(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No-op in this part, would be used in part 2 for bypassing the successor task |
||
#endif | ||
finalize(&ed); | ||
return res; | ||
} | ||
|
@@ -701,6 +705,9 @@ using detail::d1::is_current_task_group_canceling; | |
using detail::r1::missing_wait; | ||
|
||
using detail::d2::task_handle; | ||
#if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS | ||
using detail::d2::task_completion_handle; | ||
#endif | ||
} | ||
|
||
} // namespace tbb | ||
|
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK - removed my previous comment. Let's keep both copyright notices until have the guideline published. But Intel copyright year still should be 2020-2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied