mirror of
https://gcc.gnu.org/git/gcc.git
synced 2024-11-23 19:03:59 +08:00
94792057ad
Like in r12-7519-g027e30414492d50feb2854aff38227b14300dc4b, I've done git grep -v 'long long\|optab optab\|template template\|double double' | grep ' \([a-zA-Z]\+\) \1 ' This is just part of the changes, mostly for non-gcc directories. I'll try to get to the rest soon. Obviously, the above command also finds cases which are correct as is and shouldn't be changed, so one needs to manually inspect everything. I'd hope most of it is pretty obvious, but the config/ and libstdc++-v3/ hunks include a tweak in a license wording, though other copies of the similar license have the wording right. 2024-04-02 Jakub Jelinek <jakub@redhat.com> * Makefile.tpl: Fix duplicated words; returns returns -> returns. config/ * lcmessage.m4: Fix duplicated words; can can -> can, package package -> package. libdecnumber/ * decCommon.c (decFinalize): Fix duplicated words in comment; the the -> the. libgcc/ * unwind-dw2-fde.c (struct fde_accumulator): Fix duplicated words in comment; is is -> is. libgfortran/ * configure.host: Fix duplicated words; the the -> the. libgm2/ * configure.host: Fix duplicated words; the the -> the. libgomp/ * libgomp.texi (OpenMP 5.2): Fix duplicated words; with with -> with. (omp_target_associate_ptr): Fix duplicated words; either either -> either. (omp_init_allocator): Fix duplicated words; be be -> be. (omp_realloc): Fix duplicated words; is is -> is. (OMP_ALLOCATOR): Fix duplicated words; other other -> other. * priority_queue.h (priority_queue_multi_p): Fix duplicated words; to to -> to. libiberty/ * regex.c (byte_re_match_2_internal): Fix duplicated words in comment; next next -> next. * dyn-string.c (dyn_string_init): Fix duplicated words in comment; of of -> of. libitm/ * beginend.cc (GTM::gtm_thread::begin_transaction): Fix duplicated words in comment; not not -> not to. libobjc/ * init.c (duplicate_classes): Fix duplicated words in comment; in in -> in. * sendmsg.c (__objc_prepare_dtable_for_class): Fix duplicated words in comment; the the -> the. * encoding.c (objc_layout_structure): Likewise. libstdc++-v3/ * acinclude.m4: Fix duplicated words; file file -> file can. * configure.host: Fix duplicated words; the the -> the. libvtv/ * vtv_rts.cc (vtv_fail): Fix duplicated words; to to -> to. * vtv_fail.cc (vtv_fail): Likewise.
491 lines
16 KiB
C
491 lines
16 KiB
C
/* Copyright (C) 2015-2024 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 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_ */
|