OpenMP: C front end support for imperfectly-nested loops

OpenMP 5.0 removed the restriction that multiple collapsed loops must
be perfectly nested, allowing "intervening code" (including nested
BLOCKs) before or after each nested loop.  In GCC this code is moved
into the inner loop body by the respective front ends.

This patch changes the C front end to use recursive descent parsing
on nested loops within an "omp for" construct, rather than an iterative
approach, in order to preserve proper nesting of compound statements.

New common C/C++ testcases are in a separate patch.

gcc/c-family/ChangeLog
	* c-common.h (c_omp_check_loop_binding_exprs): Declare.
	* c-omp.cc: Include tree-iterator.h.
	(find_binding_in_body): New.
	(check_loop_binding_expr_r): New.
	(LOCATION_OR): New.
	(check_looop_binding_expr): New.
	(c_omp_check_loop_binding_exprs): New.

gcc/c/ChangeLog
	* c-parser.cc (struct c_parser): Add omp_for_parse_state field.
	(struct omp_for_parse_data): New.
	(check_omp_intervening_code): New.
	(add_structured_block_stmt): New.
	(c_parser_compound_statement_nostart): Recognize intervening code,
	nested loops, and other things that need special handling in
	OpenMP loop constructs.
	(c_parser_while_statement): Error on loop in intervening code.
	(c_parser_do_statement): Likewise.
	(c_parser_for_statement): Likewise.
	(c_parser_postfix_expression_after_primary): Error on calls to
	the OpenMP runtime in intervening code.
	(c_parser_pragma): Error on OpenMP pragmas in intervening code.
	(c_parser_omp_loop_nest): New.
	(c_parser_omp_for_loop): Rewrite to use recursive descent, calling
	c_parser_omp_loop_nest to do the heavy lifting.

gcc/ChangeLog
	* omp-api.h: New.
	* omp-general.cc (omp_runtime_api_procname): New.
	(omp_runtime_api_call): Moved here from omp-low.cc, and make
	non-static.
	* omp-general.h: Include omp-api.h.
	* omp-low.cc (omp_runtime_api_call): Delete this copy.

gcc/testsuite/ChangeLog
	* c-c++-common/goacc/collapse-1.c: Update for new C error behavior.
	* c-c++-common/goacc/tile-2.c: Likewise.
	* gcc.dg/gomp/collapse-1.c: Likewise.
This commit is contained in:
Sandra Loosemore 2023-08-24 17:35:00 +00:00
parent a62c8324e7
commit 143151ac20
10 changed files with 940 additions and 394 deletions

View file

@ -1299,6 +1299,7 @@ extern tree c_finish_omp_for (location_t, enum tree_code, tree, tree, tree,
extern bool c_omp_check_loop_iv (tree, tree, walk_tree_lh);
extern bool c_omp_check_loop_iv_exprs (location_t, enum tree_code, tree, int,
tree, tree, tree, walk_tree_lh);
extern bool c_omp_check_loop_binding_exprs (tree, vec<tree> *);
extern tree c_finish_oacc_wait (location_t, tree, tree);
extern tree c_oacc_split_loop_clauses (tree, tree *, bool);
extern void c_omp_split_clauses (location_t, enum tree_code, omp_clause_mask,

View file

@ -36,6 +36,7 @@ along with GCC; see the file COPYING3. If not see
#include "gimplify.h"
#include "langhooks.h"
#include "bitmap.h"
#include "tree-iterator.h"
/* Complete a #pragma oacc wait construct. LOC is the location of
@ -1728,6 +1729,156 @@ c_omp_check_loop_iv_exprs (location_t stmt_loc, enum tree_code code,
return !data.fail;
}
/* Helper function for c_omp_check_loop_binding_exprs: look for a binding
of DECL in BODY. Only traverse things that might be containers for
intervening code in an OMP loop. Returns the BIND_EXPR or DECL_EXPR
if found, otherwise null. */
static tree
find_binding_in_body (tree decl, tree body)
{
if (!body)
return NULL_TREE;
switch (TREE_CODE (body))
{
case BIND_EXPR:
for (tree b = BIND_EXPR_VARS (body); b; b = DECL_CHAIN (b))
if (b == decl)
return body;
return find_binding_in_body (decl, BIND_EXPR_BODY (body));
case DECL_EXPR:
if (DECL_EXPR_DECL (body) == decl)
return body;
return NULL_TREE;
case STATEMENT_LIST:
for (tree_stmt_iterator si = tsi_start (body); !tsi_end_p (si);
tsi_next (&si))
{
tree b = find_binding_in_body (decl, tsi_stmt (si));
if (b)
return b;
}
return NULL_TREE;
case OMP_STRUCTURED_BLOCK:
return find_binding_in_body (decl, OMP_BODY (body));
default:
return NULL_TREE;
}
}
/* Traversal function for check_loop_binding_expr, to diagnose
errors when a binding made in intervening code is referenced outside
of the loop. Returns non-null if such a reference is found. DATA points
to the tree containing the loop body. */
static tree
check_loop_binding_expr_r (tree *tp, int *walk_subtrees ATTRIBUTE_UNUSED,
void *data)
{
tree body = *(tree *)data;
if (DECL_P (*tp) && find_binding_in_body (*tp, body))
return *tp;
return NULL_TREE;
}
/* Helper macro used below. */
#define LOCATION_OR(loc1, loc2) \
((loc1) != UNKNOWN_LOCATION ? (loc1) : (loc2))
/* Check a single expression EXPR for references to variables bound in
intervening code in BODY. Return true if ok, otherwise give an error
referencing CONTEXT and return false. Use LOC for the error message
if EXPR doesn't have one. */
static bool
check_loop_binding_expr (tree expr, tree body, const char *context,
location_t loc)
{
tree bad = walk_tree (&expr, check_loop_binding_expr_r, (void *)&body, NULL);
if (bad)
{
location_t eloc = EXPR_LOCATION (expr);
error_at (LOCATION_OR (eloc, loc),
"variable %qD used %s is bound "
"in intervening code", bad, context);
return false;
}
return true;
}
/* STMT is an OMP_FOR construct. Check all of the iteration variable,
initializer, end condition, and increment for bindings inside the
loop body. If ORIG_INITS is provided, check those elements too.
Return true if OK, false otherwise. */
bool
c_omp_check_loop_binding_exprs (tree stmt, vec<tree> *orig_inits)
{
bool ok = true;
location_t loc = EXPR_LOCATION (stmt);
tree body = OMP_FOR_BODY (stmt);
int orig_init_length = orig_inits ? orig_inits->length () : 0;
for (int i = 1; i < TREE_VEC_LENGTH (OMP_FOR_INIT (stmt)); i++)
{
tree init = TREE_VEC_ELT (OMP_FOR_INIT (stmt), i);
tree cond = TREE_VEC_ELT (OMP_FOR_COND (stmt), i);
tree incr = TREE_VEC_ELT (OMP_FOR_INCR (stmt), i);
gcc_assert (TREE_CODE (init) == MODIFY_EXPR);
tree decl = TREE_OPERAND (init, 0);
tree orig_init = i < orig_init_length ? (*orig_inits)[i] : NULL_TREE;
tree e;
location_t eloc;
e = TREE_OPERAND (init, 1);
eloc = LOCATION_OR (EXPR_LOCATION (init), loc);
if (!check_loop_binding_expr (decl, body, "as loop variable", eloc))
ok = false;
if (!check_loop_binding_expr (e, body, "in initializer", eloc))
ok = false;
if (orig_init
&& !check_loop_binding_expr (orig_init, body,
"in initializer", eloc))
ok = false;
/* INCR and/or COND may be null if this is a template with a
class iterator. */
if (cond)
{
eloc = LOCATION_OR (EXPR_LOCATION (cond), loc);
if (COMPARISON_CLASS_P (cond) && TREE_OPERAND (cond, 0) == decl)
e = TREE_OPERAND (cond, 1);
else if (COMPARISON_CLASS_P (cond) && TREE_OPERAND (cond, 1) == decl)
e = TREE_OPERAND (cond, 0);
else
e = cond;
if (!check_loop_binding_expr (e, body, "in end test", eloc))
ok = false;
}
if (incr)
{
eloc = LOCATION_OR (EXPR_LOCATION (incr), loc);
/* INCR should be either a MODIFY_EXPR or pre/post
increment/decrement. We don't have to check the latter
since there are no operands besides the iteration variable. */
if (TREE_CODE (incr) == MODIFY_EXPR
&& !check_loop_binding_expr (TREE_OPERAND (incr, 1), body,
"in increment expression", eloc))
ok = false;
}
}
return ok;
}
/* This function splits clauses for OpenACC combined loop
constructs. OpenACC combined loop constructs are:
#pragma acc kernels loop

File diff suppressed because it is too large Load diff

32
gcc/omp-api.h Normal file
View file

@ -0,0 +1,32 @@
/* Functions for querying whether a function name is reserved by the
OpenMP API. This is used for error checking.
Copyright (C) 2023 Free Software Foundation, Inc.
This file is part of GCC.
GCC 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.
GCC 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.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#ifndef GCC_OMP_API_H
#define GCC_OMP_API_H
#include "coretypes.h"
/* In omp-general.cc, but declared in a separate header file for
convenience of the Fortran front end. */
extern bool omp_runtime_api_procname (const char *name);
extern bool omp_runtime_api_call (const_tree fndecl);
#endif

View file

@ -3013,4 +3013,138 @@ omp_build_component_ref (tree obj, tree field)
return ret;
}
/* Return true if NAME is the name of an omp_* runtime API call. */
bool
omp_runtime_api_procname (const char *name)
{
if (!startswith (name, "omp_"))
return false;
static const char *omp_runtime_apis[] =
{
/* This array has 3 sections. First omp_* calls that don't
have any suffixes. */
"aligned_alloc",
"aligned_calloc",
"alloc",
"calloc",
"free",
"get_mapped_ptr",
"realloc",
"target_alloc",
"target_associate_ptr",
"target_disassociate_ptr",
"target_free",
"target_is_accessible",
"target_is_present",
"target_memcpy",
"target_memcpy_async",
"target_memcpy_rect",
"target_memcpy_rect_async",
NULL,
/* Now omp_* calls that are available as omp_* and omp_*_; however, the
DECL_NAME is always omp_* without tailing underscore. */
"capture_affinity",
"destroy_allocator",
"destroy_lock",
"destroy_nest_lock",
"display_affinity",
"fulfill_event",
"get_active_level",
"get_affinity_format",
"get_cancellation",
"get_default_allocator",
"get_default_device",
"get_device_num",
"get_dynamic",
"get_initial_device",
"get_level",
"get_max_active_levels",
"get_max_task_priority",
"get_max_teams",
"get_max_threads",
"get_nested",
"get_num_devices",
"get_num_places",
"get_num_procs",
"get_num_teams",
"get_num_threads",
"get_partition_num_places",
"get_place_num",
"get_proc_bind",
"get_supported_active_levels",
"get_team_num",
"get_teams_thread_limit",
"get_thread_limit",
"get_thread_num",
"get_wtick",
"get_wtime",
"in_explicit_task",
"in_final",
"in_parallel",
"init_lock",
"init_nest_lock",
"is_initial_device",
"pause_resource",
"pause_resource_all",
"set_affinity_format",
"set_default_allocator",
"set_lock",
"set_nest_lock",
"test_lock",
"test_nest_lock",
"unset_lock",
"unset_nest_lock",
NULL,
/* And finally calls available as omp_*, omp_*_ and omp_*_8_; however,
as DECL_NAME only omp_* and omp_*_8 appear. */
"display_env",
"get_ancestor_thread_num",
"init_allocator",
"get_partition_place_nums",
"get_place_num_procs",
"get_place_proc_ids",
"get_schedule",
"get_team_size",
"set_default_device",
"set_dynamic",
"set_max_active_levels",
"set_nested",
"set_num_teams",
"set_num_threads",
"set_schedule",
"set_teams_thread_limit"
};
int mode = 0;
for (unsigned i = 0; i < ARRAY_SIZE (omp_runtime_apis); i++)
{
if (omp_runtime_apis[i] == NULL)
{
mode++;
continue;
}
size_t len = strlen (omp_runtime_apis[i]);
if (strncmp (name + 4, omp_runtime_apis[i], len) == 0
&& (name[4 + len] == '\0'
|| (mode > 1 && strcmp (name + 4 + len, "_8") == 0)))
return true;
}
return false;
}
/* Return true if FNDECL is an omp_* runtime API call. */
bool
omp_runtime_api_call (const_tree fndecl)
{
tree declname = DECL_NAME (fndecl);
if (!declname
|| (DECL_CONTEXT (fndecl) != NULL_TREE
&& TREE_CODE (DECL_CONTEXT (fndecl)) != TRANSLATION_UNIT_DECL)
|| !TREE_PUBLIC (fndecl))
return false;
return omp_runtime_api_procname (IDENTIFIER_POINTER (declname));
}
#include "gt-omp-general.h"

View file

@ -23,6 +23,7 @@ along with GCC; see the file COPYING3. If not see
#define GCC_OMP_GENERAL_H
#include "gomp-constants.h"
#include "omp-api.h"
/* Flags for an OpenACC loop. */

View file

@ -4009,135 +4009,6 @@ setjmp_or_longjmp_p (const_tree fndecl)
return !strcmp (name, "setjmp") || !strcmp (name, "longjmp");
}
/* Return true if FNDECL is an omp_* runtime API call. */
static bool
omp_runtime_api_call (const_tree fndecl)
{
tree declname = DECL_NAME (fndecl);
if (!declname
|| (DECL_CONTEXT (fndecl) != NULL_TREE
&& TREE_CODE (DECL_CONTEXT (fndecl)) != TRANSLATION_UNIT_DECL)
|| !TREE_PUBLIC (fndecl))
return false;
const char *name = IDENTIFIER_POINTER (declname);
if (!startswith (name, "omp_"))
return false;
static const char *omp_runtime_apis[] =
{
/* This array has 3 sections. First omp_* calls that don't
have any suffixes. */
"aligned_alloc",
"aligned_calloc",
"alloc",
"calloc",
"free",
"get_mapped_ptr",
"realloc",
"target_alloc",
"target_associate_ptr",
"target_disassociate_ptr",
"target_free",
"target_is_accessible",
"target_is_present",
"target_memcpy",
"target_memcpy_async",
"target_memcpy_rect",
"target_memcpy_rect_async",
NULL,
/* Now omp_* calls that are available as omp_* and omp_*_; however, the
DECL_NAME is always omp_* without tailing underscore. */
"capture_affinity",
"destroy_allocator",
"destroy_lock",
"destroy_nest_lock",
"display_affinity",
"fulfill_event",
"get_active_level",
"get_affinity_format",
"get_cancellation",
"get_default_allocator",
"get_default_device",
"get_device_num",
"get_dynamic",
"get_initial_device",
"get_level",
"get_max_active_levels",
"get_max_task_priority",
"get_max_teams",
"get_max_threads",
"get_nested",
"get_num_devices",
"get_num_places",
"get_num_procs",
"get_num_teams",
"get_num_threads",
"get_partition_num_places",
"get_place_num",
"get_proc_bind",
"get_supported_active_levels",
"get_team_num",
"get_teams_thread_limit",
"get_thread_limit",
"get_thread_num",
"get_wtick",
"get_wtime",
"in_explicit_task",
"in_final",
"in_parallel",
"init_lock",
"init_nest_lock",
"is_initial_device",
"pause_resource",
"pause_resource_all",
"set_affinity_format",
"set_default_allocator",
"set_lock",
"set_nest_lock",
"test_lock",
"test_nest_lock",
"unset_lock",
"unset_nest_lock",
NULL,
/* And finally calls available as omp_*, omp_*_ and omp_*_8_; however,
as DECL_NAME only omp_* and omp_*_8 appear. */
"display_env",
"get_ancestor_thread_num",
"init_allocator",
"get_partition_place_nums",
"get_place_num_procs",
"get_place_proc_ids",
"get_schedule",
"get_team_size",
"set_default_device",
"set_dynamic",
"set_max_active_levels",
"set_nested",
"set_num_teams",
"set_num_threads",
"set_schedule",
"set_teams_thread_limit"
};
int mode = 0;
for (unsigned i = 0; i < ARRAY_SIZE (omp_runtime_apis); i++)
{
if (omp_runtime_apis[i] == NULL)
{
mode++;
continue;
}
size_t len = strlen (omp_runtime_apis[i]);
if (strncmp (name + 4, omp_runtime_apis[i], len) == 0
&& (name[4 + len] == '\0'
|| (mode > 1 && strcmp (name + 4 + len, "_8") == 0)))
return true;
}
return false;
}
/* Helper function for scan_omp.
Callback for walk_gimple_stmt used to scan for OMP directives in

View file

@ -8,8 +8,8 @@ f1 (void)
{
#pragma acc parallel
#pragma acc loop collapse (2)
for (i = 0; i < 5; i++)
; /* { dg-error "not enough perfectly nested" } */
for (i = 0; i < 5; i++) /* { dg-error "not enough nested loops" } */
;
{
for (j = 0; j < 5; j++)
;
@ -38,9 +38,9 @@ f3 (void)
{
#pragma acc parallel
#pragma acc loop collapse (2)
for (i = 0; i < 5; i++)
for (i = 0; i < 5; i++) /* { dg-error "inner loops must be perfectly nested" } */
{
int k = foo (); /* { dg-error "not enough perfectly nested" } */
int k = foo ();
{
{
for (j = 0; j < 5; j++)
@ -56,12 +56,12 @@ f4 (void)
{
#pragma acc parallel
#pragma acc loop collapse (2)
for (i = 0; i < 5; i++)
for (i = 0; i < 5; i++) /* { dg-error "inner loops must be perfectly nested" } */
{
{
for (j = 0; j < 5; j++)
;
foo (); /* { dg-error "collapsed loops not perfectly nested before" } */
foo ();
}
}
}
@ -71,13 +71,13 @@ f5 (void)
{
#pragma acc parallel
#pragma acc loop collapse (2)
for (i = 0; i < 5; i++)
for (i = 0; i < 5; i++) /* { dg-error "inner loops must be perfectly nested" } */
{
{
for (j = 0; j < 5; j++)
;
}
foo (); /* { dg-error "collapsed loops not perfectly nested before" } */
foo ();
}
}

View file

@ -3,8 +3,8 @@ int main ()
#pragma acc parallel
{
#pragma acc loop tile (*,*)
for (int ix = 0; ix < 30; ix++)
; /* { dg-error "not enough" } */
for (int ix = 0; ix < 30; ix++) /* { dg-error "not enough" "" { target c } } */
; /* { dg-error "not enough" "" { target c++ } } */
#pragma acc loop tile (*,*)
for (int ix = 0; ix < 30; ix++)

View file

@ -8,8 +8,8 @@ void
f1 (void)
{
#pragma omp for collapse (2)
for (i = 0; i < 5; i++)
; /* { dg-error "not enough perfectly nested" } */
for (i = 0; i < 5; i++) /* { dg-error "not enough nested loops" } */
;
{
for (j = 0; j < 5; j++)
;
@ -38,7 +38,7 @@ f3 (void)
#pragma omp for collapse (2)
for (i = 0; i < 5; i++)
{
int k = foo (); /* { dg-error "not enough perfectly nested" } */
int k = foo ();
{
{
for (j = 0; j < 5; j++)
@ -58,7 +58,7 @@ f4 (void)
{
for (j = 0; j < 5; j++)
;
foo (); /* { dg-error "collapsed loops not perfectly nested before" } */
foo ();
}
}
}
@ -73,7 +73,7 @@ f5 (void)
for (j = 0; j < 5; j++)
;
}
foo (); /* { dg-error "collapsed loops not perfectly nested before" } */
foo ();
}
}