|  | /* Copyright (C) 2015-2022 Free Software Foundation, Inc. | 
|  | Contributed by Aldy Hernandez <aldyh@redhat.com>. | 
|  |  | 
|  | This file is part of the GNU Offloading and Multi Processing Library | 
|  | (libgomp). | 
|  |  | 
|  | Libgomp is free software; you can redistribute it and/or modify it | 
|  | under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3, or (at your option) | 
|  | any later version. | 
|  |  | 
|  | Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY | 
|  | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|  | FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|  | more details. | 
|  |  | 
|  | Under Section 7 of GPL version 3, you are granted additional | 
|  | permissions described in the GCC Runtime Library Exception, version | 
|  | 3.1, as published by the Free Software Foundation. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License and | 
|  | a copy of the GCC Runtime Library Exception along with this program; | 
|  | see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see | 
|  | <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | /* Header file for a priority queue of GOMP tasks.  */ | 
|  |  | 
|  | /* ?? Perhaps all the priority_tree_* functions are complex and rare | 
|  | enough to go out-of-line and be moved to priority_queue.c.  ??  */ | 
|  |  | 
|  | #ifndef _PRIORITY_QUEUE_H_ | 
|  | #define _PRIORITY_QUEUE_H_ | 
|  |  | 
|  | /* One task.  */ | 
|  |  | 
|  | struct priority_node | 
|  | { | 
|  | /* Next and previous chains in a circular doubly linked list for | 
|  | tasks within this task's priority.  */ | 
|  | struct priority_node *next, *prev; | 
|  | }; | 
|  |  | 
|  | /* All tasks within the same priority.  */ | 
|  |  | 
|  | struct priority_list | 
|  | { | 
|  | /* Priority of the tasks in this set.  */ | 
|  | int priority; | 
|  |  | 
|  | /* Tasks.  */ | 
|  | struct priority_node *tasks; | 
|  |  | 
|  | /* This points to the last of the higher priority WAITING tasks. | 
|  | Remember that for the children queue, we have: | 
|  |  | 
|  | parent_depends_on WAITING tasks. | 
|  | !parent_depends_on WAITING tasks. | 
|  | TIED tasks. | 
|  |  | 
|  | This is a pointer to the last of the parent_depends_on WAITING | 
|  | tasks which are essentially, higher priority items within their | 
|  | priority.  */ | 
|  | struct priority_node *last_parent_depends_on; | 
|  | }; | 
|  |  | 
|  | /* Another splay tree instantiation, for priority_list's.  */ | 
|  | typedef struct prio_splay_tree_node_s *prio_splay_tree_node; | 
|  | typedef struct prio_splay_tree_s *prio_splay_tree; | 
|  | typedef struct prio_splay_tree_key_s *prio_splay_tree_key; | 
|  | struct prio_splay_tree_key_s { | 
|  | /* This structure must only containing a priority_list, as we cast | 
|  | prio_splay_tree_key to priority_list throughout.  */ | 
|  | struct priority_list l; | 
|  | }; | 
|  | #define splay_tree_prefix prio | 
|  | #include "splay-tree.h" | 
|  |  | 
|  | /* The entry point into a priority queue of tasks. | 
|  |  | 
|  | There are two alternate implementations with which to store tasks: | 
|  | as a balanced tree of sorts, or as a simple list of tasks.  If | 
|  | there are only priority-0 items (ROOT is NULL), we use the simple | 
|  | list, otherwise (ROOT is non-NULL) we use the tree.  */ | 
|  |  | 
|  | struct priority_queue | 
|  | { | 
|  | /* If t.root != NULL, this is a splay tree of priority_lists to hold | 
|  | all tasks.  This is only used if multiple priorities are in play, | 
|  | otherwise we use the priority_list `l' below to hold all | 
|  | (priority-0) tasks.  */ | 
|  | struct prio_splay_tree_s t; | 
|  |  | 
|  | /* If T above is NULL, only priority-0 items exist, so keep them | 
|  | in a simple list.  */ | 
|  | struct priority_list l; | 
|  | }; | 
|  |  | 
|  | enum priority_insert_type { | 
|  | /* Insert at the beginning of a priority list.  */ | 
|  | PRIORITY_INSERT_BEGIN, | 
|  | /* Insert at the end of a priority list.  */ | 
|  | PRIORITY_INSERT_END | 
|  | }; | 
|  |  | 
|  | /* Used to determine in which queue a given priority node belongs in. | 
|  | See pnode field of gomp_task.  */ | 
|  |  | 
|  | enum priority_queue_type | 
|  | { | 
|  | PQ_TEAM,	    /* Node belongs in gomp_team's task_queue.  */ | 
|  | PQ_CHILDREN,	    /* Node belongs in parent's children_queue.  */ | 
|  | PQ_TASKGROUP,	    /* Node belongs in taskgroup->taskgroup_queue.  */ | 
|  | PQ_IGNORED = 999 | 
|  | }; | 
|  |  | 
|  | typedef bool (*priority_queue_predicate) (struct gomp_task *); | 
|  |  | 
|  | /* Priority queue implementation prototypes.  */ | 
|  |  | 
|  | extern bool priority_queue_task_in_queue_p (enum priority_queue_type, | 
|  | struct priority_queue *, | 
|  | struct gomp_task *); | 
|  | extern void priority_queue_dump (enum priority_queue_type, | 
|  | struct priority_queue *); | 
|  | extern void priority_queue_verify (enum priority_queue_type, | 
|  | struct priority_queue *, bool); | 
|  | extern struct gomp_task *priority_queue_find (enum priority_queue_type, | 
|  | struct priority_queue *, | 
|  | priority_queue_predicate); | 
|  | extern void priority_tree_remove (enum priority_queue_type, | 
|  | struct priority_queue *, | 
|  | struct priority_node *); | 
|  | extern struct gomp_task *priority_tree_next_task (enum priority_queue_type, | 
|  | struct priority_queue *, | 
|  | enum priority_queue_type, | 
|  | struct priority_queue *, | 
|  | bool *); | 
|  |  | 
|  | /* Return TRUE if there is more than one priority in HEAD.  This is | 
|  | used throughout to to choose between the fast path (priority 0 only | 
|  | items) and a world with multiple priorities.  */ | 
|  |  | 
|  | static inline bool | 
|  | priority_queue_multi_p (struct priority_queue *head) | 
|  | { | 
|  | return __builtin_expect (head->t.root != NULL, 0); | 
|  | } | 
|  |  | 
|  | /* Initialize a priority queue.  */ | 
|  |  | 
|  | static inline void | 
|  | priority_queue_init (struct priority_queue *head) | 
|  | { | 
|  | head->t.root = NULL; | 
|  | /* To save a few microseconds, we don't initialize head->l.priority | 
|  | to 0 here.  It is implied that priority will be 0 if head->t.root | 
|  | == NULL. | 
|  |  | 
|  | priority_tree_insert() will fix this when we encounter multiple | 
|  | priorities.  */ | 
|  | head->l.tasks = NULL; | 
|  | head->l.last_parent_depends_on = NULL; | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | priority_queue_free (struct priority_queue *head) | 
|  | { | 
|  | /* There's nothing to do, as tasks were freed as they were removed | 
|  | in priority_queue_remove.  */ | 
|  | } | 
|  |  | 
|  | /* Forward declarations.  */ | 
|  | static inline size_t priority_queue_offset (enum priority_queue_type); | 
|  | static inline struct gomp_task *priority_node_to_task | 
|  | (enum priority_queue_type, | 
|  | struct priority_node *); | 
|  | static inline struct priority_node *task_to_priority_node | 
|  | (enum priority_queue_type, | 
|  | struct gomp_task *); | 
|  |  | 
|  | /* Return TRUE if priority queue HEAD is empty. | 
|  |  | 
|  | MODEL IS MEMMODEL_ACQUIRE if we should use an acquire atomic to | 
|  | read from the root of the queue, otherwise MEMMODEL_RELAXED if we | 
|  | should use a plain load.  */ | 
|  |  | 
|  | static inline _Bool | 
|  | priority_queue_empty_p (struct priority_queue *head, enum memmodel model) | 
|  | { | 
|  | /* Note: The acquire barriers on the loads here synchronize with | 
|  | the write of a NULL in gomp_task_run_post_remove_parent.  It is | 
|  | not necessary that we synchronize with other non-NULL writes at | 
|  | this point, but we must ensure that all writes to memory by a | 
|  | child thread task work function are seen before we exit from | 
|  | GOMP_taskwait.  */ | 
|  | if (priority_queue_multi_p (head)) | 
|  | { | 
|  | if (model == MEMMODEL_ACQUIRE) | 
|  | return __atomic_load_n (&head->t.root, MEMMODEL_ACQUIRE) == NULL; | 
|  | return head->t.root == NULL; | 
|  | } | 
|  | if (model == MEMMODEL_ACQUIRE) | 
|  | return __atomic_load_n (&head->l.tasks, MEMMODEL_ACQUIRE) == NULL; | 
|  | return head->l.tasks == NULL; | 
|  | } | 
|  |  | 
|  | /* Look for a given PRIORITY in HEAD.  Return it if found, otherwise | 
|  | return NULL.  This only applies to the tree variant in HEAD.  There | 
|  | is no point in searching for priorities in HEAD->L.  */ | 
|  |  | 
|  | static inline struct priority_list * | 
|  | priority_queue_lookup_priority (struct priority_queue *head, int priority) | 
|  | { | 
|  | if (head->t.root == NULL) | 
|  | return NULL; | 
|  | struct prio_splay_tree_key_s k; | 
|  | k.l.priority = priority; | 
|  | return (struct priority_list *) | 
|  | prio_splay_tree_lookup (&head->t, &k); | 
|  | } | 
|  |  | 
|  | /* Insert task in DATA, with PRIORITY, in the priority list in LIST. | 
|  | LIST contains items of type TYPE. | 
|  |  | 
|  | If POS is PRIORITY_INSERT_BEGIN, the new task is inserted at the | 
|  | top of its respective priority.  If POS is PRIORITY_INSERT_END, the | 
|  | task is inserted at the end of its priority. | 
|  |  | 
|  | If ADJUST_PARENT_DEPENDS_ON is TRUE, LIST is a children queue, and | 
|  | we must keep track of higher and lower priority WAITING tasks by | 
|  | keeping the queue's last_parent_depends_on field accurate.  This | 
|  | only applies to the children queue, and the caller must ensure LIST | 
|  | is a children queue in this case. | 
|  |  | 
|  | If ADJUST_PARENT_DEPENDS_ON is TRUE, TASK_IS_PARENT_DEPENDS_ON is | 
|  | set to the task's parent_depends_on field.  If | 
|  | ADJUST_PARENT_DEPENDS_ON is FALSE, this field is irrelevant. | 
|  |  | 
|  | Return the new priority_node.  */ | 
|  |  | 
|  | static inline void | 
|  | priority_list_insert (enum priority_queue_type type, | 
|  | struct priority_list *list, | 
|  | struct gomp_task *task, | 
|  | int priority, | 
|  | enum priority_insert_type pos, | 
|  | bool adjust_parent_depends_on, | 
|  | bool task_is_parent_depends_on) | 
|  | { | 
|  | struct priority_node *node = task_to_priority_node (type, task); | 
|  | if (list->tasks) | 
|  | { | 
|  | /* If we are keeping track of higher/lower priority items, | 
|  | but this is a lower priority WAITING task | 
|  | (parent_depends_on != NULL), put it after all ready to | 
|  | run tasks.  See the comment in | 
|  | priority_queue_upgrade_task for a visual on how tasks | 
|  | should be organized.  */ | 
|  | if (adjust_parent_depends_on | 
|  | && pos == PRIORITY_INSERT_BEGIN | 
|  | && list->last_parent_depends_on | 
|  | && !task_is_parent_depends_on) | 
|  | { | 
|  | struct priority_node *last_parent_depends_on | 
|  | = list->last_parent_depends_on; | 
|  | node->next = last_parent_depends_on->next; | 
|  | node->prev = last_parent_depends_on; | 
|  | } | 
|  | /* Otherwise, put it at the top/bottom of the queue.  */ | 
|  | else | 
|  | { | 
|  | node->next = list->tasks; | 
|  | node->prev = list->tasks->prev; | 
|  | if (pos == PRIORITY_INSERT_BEGIN) | 
|  | list->tasks = node; | 
|  | } | 
|  | node->next->prev = node; | 
|  | node->prev->next = node; | 
|  | } | 
|  | else | 
|  | { | 
|  | node->next = node; | 
|  | node->prev = node; | 
|  | list->tasks = node; | 
|  | } | 
|  | if (adjust_parent_depends_on | 
|  | && list->last_parent_depends_on == NULL | 
|  | && task_is_parent_depends_on) | 
|  | list->last_parent_depends_on = node; | 
|  | } | 
|  |  | 
|  | /* Tree version of priority_list_insert.  */ | 
|  |  | 
|  | static inline void | 
|  | priority_tree_insert (enum priority_queue_type type, | 
|  | struct priority_queue *head, | 
|  | struct gomp_task *task, | 
|  | int priority, | 
|  | enum priority_insert_type pos, | 
|  | bool adjust_parent_depends_on, | 
|  | bool task_is_parent_depends_on) | 
|  | { | 
|  | if (__builtin_expect (head->t.root == NULL, 0)) | 
|  | { | 
|  | /* The first time around, transfer any priority 0 items to the | 
|  | tree.  */ | 
|  | if (head->l.tasks != NULL) | 
|  | { | 
|  | prio_splay_tree_node k = gomp_malloc (sizeof (*k)); | 
|  | k->left = NULL; | 
|  | k->right = NULL; | 
|  | k->key.l.priority = 0; | 
|  | k->key.l.tasks = head->l.tasks; | 
|  | k->key.l.last_parent_depends_on = head->l.last_parent_depends_on; | 
|  | prio_splay_tree_insert (&head->t, k); | 
|  | head->l.tasks = NULL; | 
|  | } | 
|  | } | 
|  | struct priority_list *list | 
|  | = priority_queue_lookup_priority (head, priority); | 
|  | if (!list) | 
|  | { | 
|  | prio_splay_tree_node k = gomp_malloc (sizeof (*k)); | 
|  | k->left = NULL; | 
|  | k->right = NULL; | 
|  | k->key.l.priority = priority; | 
|  | k->key.l.tasks = NULL; | 
|  | k->key.l.last_parent_depends_on = NULL; | 
|  | prio_splay_tree_insert (&head->t, k); | 
|  | list = &k->key.l; | 
|  | } | 
|  | priority_list_insert (type, list, task, priority, pos, | 
|  | adjust_parent_depends_on, | 
|  | task_is_parent_depends_on); | 
|  | } | 
|  |  | 
|  | /* Generic version of priority_*_insert.  */ | 
|  |  | 
|  | static inline void | 
|  | priority_queue_insert (enum priority_queue_type type, | 
|  | struct priority_queue *head, | 
|  | struct gomp_task *task, | 
|  | int priority, | 
|  | enum priority_insert_type pos, | 
|  | bool adjust_parent_depends_on, | 
|  | bool task_is_parent_depends_on) | 
|  | { | 
|  | #if _LIBGOMP_CHECKING_ | 
|  | if (priority_queue_task_in_queue_p (type, head, task)) | 
|  | gomp_fatal ("Attempt to insert existing task %p", task); | 
|  | #endif | 
|  | if (priority_queue_multi_p (head) || __builtin_expect (priority > 0, 0)) | 
|  | priority_tree_insert (type, head, task, priority, pos, | 
|  | adjust_parent_depends_on, | 
|  | task_is_parent_depends_on); | 
|  | else | 
|  | priority_list_insert (type, &head->l, task, priority, pos, | 
|  | adjust_parent_depends_on, | 
|  | task_is_parent_depends_on); | 
|  | } | 
|  |  | 
|  | /* If multiple priorities are in play, return the highest priority | 
|  | task from within Q1 and Q2, while giving preference to tasks from | 
|  | Q1.  If the returned task is chosen from Q1, *Q1_CHOSEN_P is set to | 
|  | TRUE, otherwise it is set to FALSE. | 
|  |  | 
|  | If multiple priorities are not in play (only 0 priorities are | 
|  | available), the next task is chosen exclusively from Q1. | 
|  |  | 
|  | As a special case, Q2 can be NULL, in which case, we just choose | 
|  | the highest priority WAITING task in Q1.  This is an optimization | 
|  | to speed up looking through only one queue. | 
|  |  | 
|  | We assume Q1 has at least one item.  */ | 
|  |  | 
|  | static inline struct gomp_task * | 
|  | priority_queue_next_task (enum priority_queue_type t1, | 
|  | struct priority_queue *q1, | 
|  | enum priority_queue_type t2, | 
|  | struct priority_queue *q2, | 
|  | bool *q1_chosen_p) | 
|  | { | 
|  | #if _LIBGOMP_CHECKING_ | 
|  | if (priority_queue_empty_p (q1, MEMMODEL_RELAXED)) | 
|  | gomp_fatal ("priority_queue_next_task: Q1 is empty"); | 
|  | #endif | 
|  | if (priority_queue_multi_p (q1)) | 
|  | { | 
|  | struct gomp_task *t | 
|  | = priority_tree_next_task (t1, q1, t2, q2, q1_chosen_p); | 
|  | /* If T is NULL, there are no WAITING tasks in Q1.  In which | 
|  | case, return any old (non-waiting) task which will cause the | 
|  | caller to do the right thing when checking T->KIND == | 
|  | GOMP_TASK_WAITING.  */ | 
|  | if (!t) | 
|  | { | 
|  | #if _LIBGOMP_CHECKING_ | 
|  | if (*q1_chosen_p == false) | 
|  | gomp_fatal ("priority_queue_next_task inconsistency"); | 
|  | #endif | 
|  | return priority_node_to_task (t1, q1->t.root->key.l.tasks); | 
|  | } | 
|  | return t; | 
|  | } | 
|  | else | 
|  | { | 
|  | *q1_chosen_p = true; | 
|  | return priority_node_to_task (t1, q1->l.tasks); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Remove NODE from LIST. | 
|  |  | 
|  | If we are removing the one and only item in the list, and MODEL is | 
|  | MEMMODEL_RELEASE, use an atomic release to clear the list. | 
|  |  | 
|  | If the list becomes empty after the remove, return TRUE.  */ | 
|  |  | 
|  | static inline bool | 
|  | priority_list_remove (struct priority_list *list, | 
|  | struct priority_node *node, | 
|  | enum memmodel model) | 
|  | { | 
|  | bool empty = false; | 
|  | node->prev->next = node->next; | 
|  | node->next->prev = node->prev; | 
|  | if (list->tasks == node) | 
|  | { | 
|  | if (node->next != node) | 
|  | list->tasks = node->next; | 
|  | else | 
|  | { | 
|  | /* We access task->children in GOMP_taskwait outside of | 
|  | the task lock mutex region, so need a release barrier | 
|  | here to ensure memory written by child_task->fn above | 
|  | is flushed before the NULL is written.  */ | 
|  | if (model == MEMMODEL_RELEASE) | 
|  | __atomic_store_n (&list->tasks, NULL, MEMMODEL_RELEASE); | 
|  | else | 
|  | list->tasks = NULL; | 
|  | empty = true; | 
|  | goto remove_out; | 
|  | } | 
|  | } | 
|  | remove_out: | 
|  | #if _LIBGOMP_CHECKING_ | 
|  | memset (node, 0xaf, sizeof (*node)); | 
|  | #endif | 
|  | return empty; | 
|  | } | 
|  |  | 
|  | /* This is the generic version of priority_list_remove. | 
|  |  | 
|  | Remove NODE from priority queue HEAD.  HEAD contains tasks of type TYPE. | 
|  |  | 
|  | If we are removing the one and only item in the priority queue and | 
|  | MODEL is MEMMODEL_RELEASE, use an atomic release to clear the queue. | 
|  |  | 
|  | If the queue becomes empty after the remove, return TRUE.  */ | 
|  |  | 
|  | static inline bool | 
|  | priority_queue_remove (enum priority_queue_type type, | 
|  | struct priority_queue *head, | 
|  | struct gomp_task *task, | 
|  | enum memmodel model) | 
|  | { | 
|  | #if _LIBGOMP_CHECKING_ | 
|  | if (!priority_queue_task_in_queue_p (type, head, task)) | 
|  | gomp_fatal ("Attempt to remove missing task %p", task); | 
|  | #endif | 
|  | if (priority_queue_multi_p (head)) | 
|  | { | 
|  | priority_tree_remove (type, head, task_to_priority_node (type, task)); | 
|  | if (head->t.root == NULL) | 
|  | { | 
|  | if (model == MEMMODEL_RELEASE) | 
|  | /* Errr, we store NULL twice, the alternative would be to | 
|  | use an atomic release directly in the splay tree | 
|  | routines.  Worth it?  */ | 
|  | __atomic_store_n (&head->t.root, NULL, MEMMODEL_RELEASE); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  | else | 
|  | return priority_list_remove (&head->l, | 
|  | task_to_priority_node (type, task), model); | 
|  | } | 
|  |  | 
|  | #endif /* _PRIORITY_QUEUE_H_ */ |