tree-optimization/100923 - re-do VN with contextual PTA info fix
[official-gcc.git] / libgomp / priority_queue.h
blobf0c51de6d1cfbf7cd5ad04301df5fb7239b30dea
1 /* Copyright (C) 2015-2024 Free Software Foundation, Inc.
2 Contributed by Aldy Hernandez <aldyh@redhat.com>.
4 This file is part of the GNU Offloading and Multi Processing Library
5 (libgomp).
7 Libgomp is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3, or (at your option)
10 any later version.
12 Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 more details.
17 Under Section 7 of GPL version 3, you are granted additional
18 permissions described in the GCC Runtime Library Exception, version
19 3.1, as published by the Free Software Foundation.
21 You should have received a copy of the GNU General Public License and
22 a copy of the GCC Runtime Library Exception along with this program;
23 see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
24 <http://www.gnu.org/licenses/>. */
26 /* Header file for a priority queue of GOMP tasks. */
28 /* ?? Perhaps all the priority_tree_* functions are complex and rare
29 enough to go out-of-line and be moved to priority_queue.c. ?? */
31 #ifndef _PRIORITY_QUEUE_H_
32 #define _PRIORITY_QUEUE_H_
34 /* One task. */
36 struct priority_node
38 /* Next and previous chains in a circular doubly linked list for
39 tasks within this task's priority. */
40 struct priority_node *next, *prev;
43 /* All tasks within the same priority. */
45 struct priority_list
47 /* Priority of the tasks in this set. */
48 int priority;
50 /* Tasks. */
51 struct priority_node *tasks;
53 /* This points to the last of the higher priority WAITING tasks.
54 Remember that for the children queue, we have:
56 parent_depends_on WAITING tasks.
57 !parent_depends_on WAITING tasks.
58 TIED tasks.
60 This is a pointer to the last of the parent_depends_on WAITING
61 tasks which are essentially, higher priority items within their
62 priority. */
63 struct priority_node *last_parent_depends_on;
66 /* Another splay tree instantiation, for priority_list's. */
67 typedef struct prio_splay_tree_node_s *prio_splay_tree_node;
68 typedef struct prio_splay_tree_s *prio_splay_tree;
69 typedef struct prio_splay_tree_key_s *prio_splay_tree_key;
70 struct prio_splay_tree_key_s {
71 /* This structure must only containing a priority_list, as we cast
72 prio_splay_tree_key to priority_list throughout. */
73 struct priority_list l;
75 #define splay_tree_prefix prio
76 #include "splay-tree.h"
78 /* The entry point into a priority queue of tasks.
80 There are two alternate implementations with which to store tasks:
81 as a balanced tree of sorts, or as a simple list of tasks. If
82 there are only priority-0 items (ROOT is NULL), we use the simple
83 list, otherwise (ROOT is non-NULL) we use the tree. */
85 struct priority_queue
87 /* If t.root != NULL, this is a splay tree of priority_lists to hold
88 all tasks. This is only used if multiple priorities are in play,
89 otherwise we use the priority_list `l' below to hold all
90 (priority-0) tasks. */
91 struct prio_splay_tree_s t;
93 /* If T above is NULL, only priority-0 items exist, so keep them
94 in a simple list. */
95 struct priority_list l;
98 enum priority_insert_type {
99 /* Insert at the beginning of a priority list. */
100 PRIORITY_INSERT_BEGIN,
101 /* Insert at the end of a priority list. */
102 PRIORITY_INSERT_END
105 /* Used to determine in which queue a given priority node belongs in.
106 See pnode field of gomp_task. */
108 enum priority_queue_type
110 PQ_TEAM, /* Node belongs in gomp_team's task_queue. */
111 PQ_CHILDREN, /* Node belongs in parent's children_queue. */
112 PQ_TASKGROUP, /* Node belongs in taskgroup->taskgroup_queue. */
113 PQ_IGNORED = 999
116 typedef bool (*priority_queue_predicate) (struct gomp_task *);
118 /* Priority queue implementation prototypes. */
120 extern bool priority_queue_task_in_queue_p (enum priority_queue_type,
121 struct priority_queue *,
122 struct gomp_task *);
123 extern void priority_queue_dump (enum priority_queue_type,
124 struct priority_queue *);
125 extern void priority_queue_verify (enum priority_queue_type,
126 struct priority_queue *, bool);
127 extern struct gomp_task *priority_queue_find (enum priority_queue_type,
128 struct priority_queue *,
129 priority_queue_predicate);
130 extern void priority_tree_remove (enum priority_queue_type,
131 struct priority_queue *,
132 struct priority_node *);
133 extern struct gomp_task *priority_tree_next_task (enum priority_queue_type,
134 struct priority_queue *,
135 enum priority_queue_type,
136 struct priority_queue *,
137 bool *);
139 /* Return TRUE if there is more than one priority in HEAD. This is
140 used throughout to choose between the fast path (priority 0 only
141 items) and a world with multiple priorities. */
143 static inline bool
144 priority_queue_multi_p (struct priority_queue *head)
146 return __builtin_expect (head->t.root != NULL, 0);
149 /* Initialize a priority queue. */
151 static inline void
152 priority_queue_init (struct priority_queue *head)
154 head->t.root = NULL;
155 /* To save a few microseconds, we don't initialize head->l.priority
156 to 0 here. It is implied that priority will be 0 if head->t.root
157 == NULL.
159 priority_tree_insert() will fix this when we encounter multiple
160 priorities. */
161 head->l.tasks = NULL;
162 head->l.last_parent_depends_on = NULL;
165 static inline void
166 priority_queue_free (struct priority_queue *head)
168 /* There's nothing to do, as tasks were freed as they were removed
169 in priority_queue_remove. */
172 /* Forward declarations. */
173 static inline size_t priority_queue_offset (enum priority_queue_type);
174 static inline struct gomp_task *priority_node_to_task
175 (enum priority_queue_type,
176 struct priority_node *);
177 static inline struct priority_node *task_to_priority_node
178 (enum priority_queue_type,
179 struct gomp_task *);
181 /* Return TRUE if priority queue HEAD is empty.
183 MODEL IS MEMMODEL_ACQUIRE if we should use an acquire atomic to
184 read from the root of the queue, otherwise MEMMODEL_RELAXED if we
185 should use a plain load. */
187 static inline _Bool
188 priority_queue_empty_p (struct priority_queue *head, enum memmodel model)
190 /* Note: The acquire barriers on the loads here synchronize with
191 the write of a NULL in gomp_task_run_post_remove_parent. It is
192 not necessary that we synchronize with other non-NULL writes at
193 this point, but we must ensure that all writes to memory by a
194 child thread task work function are seen before we exit from
195 GOMP_taskwait. */
196 if (priority_queue_multi_p (head))
198 if (model == MEMMODEL_ACQUIRE)
199 return __atomic_load_n (&head->t.root, MEMMODEL_ACQUIRE) == NULL;
200 return head->t.root == NULL;
202 if (model == MEMMODEL_ACQUIRE)
203 return __atomic_load_n (&head->l.tasks, MEMMODEL_ACQUIRE) == NULL;
204 return head->l.tasks == NULL;
207 /* Look for a given PRIORITY in HEAD. Return it if found, otherwise
208 return NULL. This only applies to the tree variant in HEAD. There
209 is no point in searching for priorities in HEAD->L. */
211 static inline struct priority_list *
212 priority_queue_lookup_priority (struct priority_queue *head, int priority)
214 if (head->t.root == NULL)
215 return NULL;
216 struct prio_splay_tree_key_s k;
217 k.l.priority = priority;
218 return (struct priority_list *)
219 prio_splay_tree_lookup (&head->t, &k);
222 /* Insert task in DATA, with PRIORITY, in the priority list in LIST.
223 LIST contains items of type TYPE.
225 If POS is PRIORITY_INSERT_BEGIN, the new task is inserted at the
226 top of its respective priority. If POS is PRIORITY_INSERT_END, the
227 task is inserted at the end of its priority.
229 If ADJUST_PARENT_DEPENDS_ON is TRUE, LIST is a children queue, and
230 we must keep track of higher and lower priority WAITING tasks by
231 keeping the queue's last_parent_depends_on field accurate. This
232 only applies to the children queue, and the caller must ensure LIST
233 is a children queue in this case.
235 If ADJUST_PARENT_DEPENDS_ON is TRUE, TASK_IS_PARENT_DEPENDS_ON is
236 set to the task's parent_depends_on field. If
237 ADJUST_PARENT_DEPENDS_ON is FALSE, this field is irrelevant.
239 Return the new priority_node. */
241 static inline void
242 priority_list_insert (enum priority_queue_type type,
243 struct priority_list *list,
244 struct gomp_task *task,
245 int priority,
246 enum priority_insert_type pos,
247 bool adjust_parent_depends_on,
248 bool task_is_parent_depends_on)
250 struct priority_node *node = task_to_priority_node (type, task);
251 if (list->tasks)
253 /* If we are keeping track of higher/lower priority items,
254 but this is a lower priority WAITING task
255 (parent_depends_on != NULL), put it after all ready to
256 run tasks. See the comment in
257 priority_queue_upgrade_task for a visual on how tasks
258 should be organized. */
259 if (adjust_parent_depends_on
260 && pos == PRIORITY_INSERT_BEGIN
261 && list->last_parent_depends_on
262 && !task_is_parent_depends_on)
264 struct priority_node *last_parent_depends_on
265 = list->last_parent_depends_on;
266 node->next = last_parent_depends_on->next;
267 node->prev = last_parent_depends_on;
269 /* Otherwise, put it at the top/bottom of the queue. */
270 else
272 node->next = list->tasks;
273 node->prev = list->tasks->prev;
274 if (pos == PRIORITY_INSERT_BEGIN)
275 list->tasks = node;
277 node->next->prev = node;
278 node->prev->next = node;
280 else
282 node->next = node;
283 node->prev = node;
284 list->tasks = node;
286 if (adjust_parent_depends_on
287 && list->last_parent_depends_on == NULL
288 && task_is_parent_depends_on)
289 list->last_parent_depends_on = node;
292 /* Tree version of priority_list_insert. */
294 static inline void
295 priority_tree_insert (enum priority_queue_type type,
296 struct priority_queue *head,
297 struct gomp_task *task,
298 int priority,
299 enum priority_insert_type pos,
300 bool adjust_parent_depends_on,
301 bool task_is_parent_depends_on)
303 if (__builtin_expect (head->t.root == NULL, 0))
305 /* The first time around, transfer any priority 0 items to the
306 tree. */
307 if (head->l.tasks != NULL)
309 prio_splay_tree_node k = gomp_malloc (sizeof (*k));
310 k->left = NULL;
311 k->right = NULL;
312 k->key.l.priority = 0;
313 k->key.l.tasks = head->l.tasks;
314 k->key.l.last_parent_depends_on = head->l.last_parent_depends_on;
315 prio_splay_tree_insert (&head->t, k);
316 head->l.tasks = NULL;
319 struct priority_list *list
320 = priority_queue_lookup_priority (head, priority);
321 if (!list)
323 prio_splay_tree_node k = gomp_malloc (sizeof (*k));
324 k->left = NULL;
325 k->right = NULL;
326 k->key.l.priority = priority;
327 k->key.l.tasks = NULL;
328 k->key.l.last_parent_depends_on = NULL;
329 prio_splay_tree_insert (&head->t, k);
330 list = &k->key.l;
332 priority_list_insert (type, list, task, priority, pos,
333 adjust_parent_depends_on,
334 task_is_parent_depends_on);
337 /* Generic version of priority_*_insert. */
339 static inline void
340 priority_queue_insert (enum priority_queue_type type,
341 struct priority_queue *head,
342 struct gomp_task *task,
343 int priority,
344 enum priority_insert_type pos,
345 bool adjust_parent_depends_on,
346 bool task_is_parent_depends_on)
348 #if _LIBGOMP_CHECKING_
349 if (priority_queue_task_in_queue_p (type, head, task))
350 gomp_fatal ("Attempt to insert existing task %p", task);
351 #endif
352 if (priority_queue_multi_p (head) || __builtin_expect (priority > 0, 0))
353 priority_tree_insert (type, head, task, priority, pos,
354 adjust_parent_depends_on,
355 task_is_parent_depends_on);
356 else
357 priority_list_insert (type, &head->l, task, priority, pos,
358 adjust_parent_depends_on,
359 task_is_parent_depends_on);
362 /* If multiple priorities are in play, return the highest priority
363 task from within Q1 and Q2, while giving preference to tasks from
364 Q1. If the returned task is chosen from Q1, *Q1_CHOSEN_P is set to
365 TRUE, otherwise it is set to FALSE.
367 If multiple priorities are not in play (only 0 priorities are
368 available), the next task is chosen exclusively from Q1.
370 As a special case, Q2 can be NULL, in which case, we just choose
371 the highest priority WAITING task in Q1. This is an optimization
372 to speed up looking through only one queue.
374 We assume Q1 has at least one item. */
376 static inline struct gomp_task *
377 priority_queue_next_task (enum priority_queue_type t1,
378 struct priority_queue *q1,
379 enum priority_queue_type t2,
380 struct priority_queue *q2,
381 bool *q1_chosen_p)
383 #if _LIBGOMP_CHECKING_
384 if (priority_queue_empty_p (q1, MEMMODEL_RELAXED))
385 gomp_fatal ("priority_queue_next_task: Q1 is empty");
386 #endif
387 if (priority_queue_multi_p (q1))
389 struct gomp_task *t
390 = priority_tree_next_task (t1, q1, t2, q2, q1_chosen_p);
391 /* If T is NULL, there are no WAITING tasks in Q1. In which
392 case, return any old (non-waiting) task which will cause the
393 caller to do the right thing when checking T->KIND ==
394 GOMP_TASK_WAITING. */
395 if (!t)
397 #if _LIBGOMP_CHECKING_
398 if (*q1_chosen_p == false)
399 gomp_fatal ("priority_queue_next_task inconsistency");
400 #endif
401 return priority_node_to_task (t1, q1->t.root->key.l.tasks);
403 return t;
405 else
407 *q1_chosen_p = true;
408 return priority_node_to_task (t1, q1->l.tasks);
412 /* Remove NODE from LIST.
414 If we are removing the one and only item in the list, and MODEL is
415 MEMMODEL_RELEASE, use an atomic release to clear the list.
417 If the list becomes empty after the remove, return TRUE. */
419 static inline bool
420 priority_list_remove (struct priority_list *list,
421 struct priority_node *node,
422 enum memmodel model)
424 bool empty = false;
425 node->prev->next = node->next;
426 node->next->prev = node->prev;
427 if (list->tasks == node)
429 if (node->next != node)
430 list->tasks = node->next;
431 else
433 /* We access task->children in GOMP_taskwait outside of
434 the task lock mutex region, so need a release barrier
435 here to ensure memory written by child_task->fn above
436 is flushed before the NULL is written. */
437 if (model == MEMMODEL_RELEASE)
438 __atomic_store_n (&list->tasks, NULL, MEMMODEL_RELEASE);
439 else
440 list->tasks = NULL;
441 empty = true;
442 goto remove_out;
445 remove_out:
446 #if _LIBGOMP_CHECKING_
447 memset (node, 0xaf, sizeof (*node));
448 #endif
449 return empty;
452 /* This is the generic version of priority_list_remove.
454 Remove NODE from priority queue HEAD. HEAD contains tasks of type TYPE.
456 If we are removing the one and only item in the priority queue and
457 MODEL is MEMMODEL_RELEASE, use an atomic release to clear the queue.
459 If the queue becomes empty after the remove, return TRUE. */
461 static inline bool
462 priority_queue_remove (enum priority_queue_type type,
463 struct priority_queue *head,
464 struct gomp_task *task,
465 enum memmodel model)
467 #if _LIBGOMP_CHECKING_
468 if (!priority_queue_task_in_queue_p (type, head, task))
469 gomp_fatal ("Attempt to remove missing task %p", task);
470 #endif
471 if (priority_queue_multi_p (head))
473 priority_tree_remove (type, head, task_to_priority_node (type, task));
474 if (head->t.root == NULL)
476 if (model == MEMMODEL_RELEASE)
477 /* Errr, we store NULL twice, the alternative would be to
478 use an atomic release directly in the splay tree
479 routines. Worth it? */
480 __atomic_store_n (&head->t.root, NULL, MEMMODEL_RELEASE);
481 return true;
483 return false;
485 else
486 return priority_list_remove (&head->l,
487 task_to_priority_node (type, task), model);
490 #endif /* _PRIORITY_QUEUE_H_ */