| /* 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/>. */ |
| |
| /* Priority queue implementation of GOMP tasks. */ |
| |
| #include "libgomp.h" |
| |
| #if _LIBGOMP_CHECKING_ |
| #include <stdio.h> |
| |
| /* Sanity check to verify whether a TASK is in LIST. Return TRUE if |
| found, FALSE otherwise. |
| |
| TYPE is the type of priority queue this task resides in. */ |
| |
| static inline bool |
| priority_queue_task_in_list_p (enum priority_queue_type type, |
| struct priority_list *list, |
| struct gomp_task *task) |
| { |
| struct priority_node *p = list->tasks; |
| do |
| { |
| if (priority_node_to_task (type, p) == task) |
| return true; |
| p = p->next; |
| } |
| while (p != list->tasks); |
| return false; |
| } |
| |
| /* Tree version of priority_queue_task_in_list_p. */ |
| |
| static inline bool |
| priority_queue_task_in_tree_p (enum priority_queue_type type, |
| struct priority_queue *head, |
| struct gomp_task *task) |
| { |
| struct priority_list *list |
| = priority_queue_lookup_priority (head, task->priority); |
| if (!list) |
| return false; |
| return priority_queue_task_in_list_p (type, list, task); |
| } |
| |
| /* Generic version of priority_queue_task_in_list_p that works for |
| trees or lists. */ |
| |
| bool |
| priority_queue_task_in_queue_p (enum priority_queue_type type, |
| struct priority_queue *head, |
| struct gomp_task *task) |
| { |
| if (priority_queue_empty_p (head, MEMMODEL_RELAXED)) |
| return false; |
| if (priority_queue_multi_p (head)) |
| return priority_queue_task_in_tree_p (type, head, task); |
| else |
| return priority_queue_task_in_list_p (type, &head->l, task); |
| } |
| |
| /* Sanity check LIST to make sure the tasks therein are in the right |
| order. LIST is a priority list of type TYPE. |
| |
| The expected order is that GOMP_TASK_WAITING tasks come before |
| GOMP_TASK_TIED/GOMP_TASK_ASYNC_RUNNING ones. |
| |
| If CHECK_DEPS is TRUE, we also check that parent_depends_on WAITING |
| tasks come before !parent_depends_on WAITING tasks. This is only |
| applicable to the children queue, and the caller is expected to |
| ensure that we are verifying the children queue. */ |
| |
| static void |
| priority_list_verify (enum priority_queue_type type, |
| struct priority_list *list, bool check_deps) |
| { |
| bool seen_tied = false; |
| bool seen_plain_waiting = false; |
| struct priority_node *p = list->tasks; |
| while (1) |
| { |
| struct gomp_task *t = priority_node_to_task (type, p); |
| if (seen_tied && t->kind == GOMP_TASK_WAITING) |
| gomp_fatal ("priority_queue_verify: WAITING task after TIED"); |
| if (t->kind >= GOMP_TASK_TIED) |
| seen_tied = true; |
| else if (check_deps && t->kind == GOMP_TASK_WAITING) |
| { |
| if (t->parent_depends_on) |
| { |
| if (seen_plain_waiting) |
| gomp_fatal ("priority_queue_verify: " |
| "parent_depends_on after !parent_depends_on"); |
| } |
| else |
| seen_plain_waiting = true; |
| } |
| p = p->next; |
| if (p == list->tasks) |
| break; |
| } |
| } |
| |
| /* Callback type for priority_tree_verify_callback. */ |
| struct cbtype |
| { |
| enum priority_queue_type type; |
| bool check_deps; |
| }; |
| |
| /* Verify every task in NODE. |
| |
| Callback for splay_tree_foreach. */ |
| |
| static void |
| priority_tree_verify_callback (prio_splay_tree_key key, void *data) |
| { |
| struct cbtype *cb = (struct cbtype *) data; |
| priority_list_verify (cb->type, &key->l, cb->check_deps); |
| } |
| |
| /* Generic version of priority_list_verify. |
| |
| Sanity check HEAD to make sure the tasks therein are in the right |
| order. The priority_queue holds tasks of type TYPE. |
| |
| If CHECK_DEPS is TRUE, we also check that parent_depends_on WAITING |
| tasks come before !parent_depends_on WAITING tasks. This is only |
| applicable to the children queue, and the caller is expected to |
| ensure that we are verifying the children queue. */ |
| |
| void |
| priority_queue_verify (enum priority_queue_type type, |
| struct priority_queue *head, bool check_deps) |
| { |
| if (priority_queue_empty_p (head, MEMMODEL_RELAXED)) |
| return; |
| if (priority_queue_multi_p (head)) |
| { |
| struct cbtype cb = { type, check_deps }; |
| prio_splay_tree_foreach (&head->t, |
| priority_tree_verify_callback, &cb); |
| } |
| else |
| priority_list_verify (type, &head->l, check_deps); |
| } |
| #endif /* _LIBGOMP_CHECKING_ */ |
| |
| /* Tree version of priority_queue_find. */ |
| |
| static struct gomp_task * |
| priority_tree_find (enum priority_queue_type type, |
| prio_splay_tree_node node, |
| priority_queue_predicate pred) |
| { |
| again: |
| if (!node) |
| return NULL; |
| struct gomp_task *task = priority_tree_find (type, node->right, pred); |
| if (task) |
| return task; |
| task = priority_node_to_task (type, node->key.l.tasks); |
| if (pred (task)) |
| return task; |
| node = node->left; |
| goto again; |
| } |
| |
| /* List version of priority_queue_find. */ |
| |
| static struct gomp_task * |
| priority_list_find (enum priority_queue_type type, |
| struct priority_list *list, |
| priority_queue_predicate pred) |
| { |
| struct priority_node *node = list->tasks; |
| if (!node) |
| return NULL; |
| |
| do |
| { |
| struct gomp_task *task = priority_node_to_task (type, node); |
| if (pred (task)) |
| return task; |
| node = node->next; |
| } |
| while (node != list->tasks); |
| |
| return NULL; |
| } |
| |
| /* Return the highest priority task in the priority queue HEAD that |
| satisfies the predicate PRED. HEAD contains tasks of type TYPE. */ |
| |
| struct gomp_task * |
| priority_queue_find (enum priority_queue_type type, |
| struct priority_queue *head, |
| priority_queue_predicate pred) |
| { |
| if (priority_queue_multi_p (head)) |
| return priority_tree_find (type, head->t.root, pred); |
| else |
| return priority_list_find (type, &head->l, pred); |
| } |
| |
| /* Remove NODE from priority queue HEAD, wherever it may be inside the |
| tree. HEAD contains tasks of type TYPE. */ |
| |
| void |
| priority_tree_remove (enum priority_queue_type type, |
| struct priority_queue *head, |
| struct priority_node *node) |
| { |
| /* ?? The only reason this function is not inlined is because we |
| need to find the priority within gomp_task (which has not been |
| completely defined in the header file). If the lack of inlining |
| is a concern, we could pass the priority number as a |
| parameter, or we could move this to libgomp.h. */ |
| int priority = priority_node_to_task (type, node)->priority; |
| |
| /* ?? We could avoid this lookup by keeping a pointer to the key in |
| the priority_node. */ |
| struct priority_list *list |
| = priority_queue_lookup_priority (head, priority); |
| #if _LIBGOMP_CHECKING_ |
| if (!list) |
| gomp_fatal ("Unable to find priority %d", priority); |
| #endif |
| /* If NODE was the last in its priority, clean up the priority. */ |
| if (priority_list_remove (list, node, MEMMODEL_RELAXED)) |
| { |
| prio_splay_tree_remove (&head->t, (prio_splay_tree_key) list); |
| list->tasks = NULL; |
| #if _LIBGOMP_CHECKING_ |
| memset (list, 0xaf, sizeof (*list)); |
| #endif |
| free (list); |
| } |
| } |
| |
| /* Return the highest priority WAITING task in a splay tree NODE. If |
| there are no WAITING tasks available, return NULL. |
| |
| NODE is a priority list containing tasks of type TYPE. |
| |
| The right most node in a tree contains the highest priority. |
| Recurse down to find such a node. If the task at that max node is |
| not WAITING, bubble back up and look at the remaining tasks |
| in-order. */ |
| |
| static struct gomp_task * |
| priority_tree_next_task_1 (enum priority_queue_type type, |
| prio_splay_tree_node node) |
| { |
| again: |
| if (!node) |
| return NULL; |
| struct gomp_task *ret = priority_tree_next_task_1 (type, node->right); |
| if (ret) |
| return ret; |
| ret = priority_node_to_task (type, node->key.l.tasks); |
| if (ret->kind == GOMP_TASK_WAITING) |
| return ret; |
| node = node->left; |
| goto again; |
| } |
| |
| /* Return the highest priority WAITING task from within Q1 and Q2, |
| while giving preference to tasks from Q1. Q1 is a queue containing |
| items of type TYPE1. Q2 is a queue containing items of type TYPE2. |
| |
| Since we are mostly interested in Q1, if there are no WAITING tasks |
| in Q1, we don't bother checking Q2, and just return NULL. |
| |
| 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. |
| |
| If the returned task is chosen from Q1, *Q1_CHOSEN_P is set to |
| TRUE, otherwise it is set to FALSE. */ |
| |
| struct gomp_task * |
| priority_tree_next_task (enum priority_queue_type type1, |
| struct priority_queue *q1, |
| enum priority_queue_type type2, |
| struct priority_queue *q2, |
| bool *q1_chosen_p) |
| { |
| struct gomp_task *t1 = priority_tree_next_task_1 (type1, q1->t.root); |
| if (!t1 |
| /* Special optimization when only searching through one queue. */ |
| || !q2) |
| { |
| *q1_chosen_p = true; |
| return t1; |
| } |
| struct gomp_task *t2 = priority_tree_next_task_1 (type2, q2->t.root); |
| if (!t2 || t1->priority > t2->priority) |
| { |
| *q1_chosen_p = true; |
| return t1; |
| } |
| if (t2->priority > t1->priority) |
| { |
| *q1_chosen_p = false; |
| return t2; |
| } |
| /* If we get here, the priorities are the same, so we must look at |
| parent_depends_on to make our decision. */ |
| #if _LIBGOMP_CHECKING_ |
| if (t1 != t2) |
| gomp_fatal ("priority_tree_next_task: t1 != t2"); |
| #endif |
| if (t2->parent_depends_on && !t1->parent_depends_on) |
| { |
| *q1_chosen_p = false; |
| return t2; |
| } |
| *q1_chosen_p = true; |
| return t1; |
| } |
| |
| /* Priority splay trees comparison function. */ |
| static inline int |
| prio_splay_compare (prio_splay_tree_key x, prio_splay_tree_key y) |
| { |
| if (x->l.priority == y->l.priority) |
| return 0; |
| return x->l.priority < y->l.priority ? -1 : 1; |
| } |
| |
| /* Define another splay tree instantiation, for priority_list's. */ |
| #define splay_tree_prefix prio |
| #define splay_tree_c |
| #include "splay-tree.h" |