On Mon, Nov 20, 2023 at 11:42:02AM +0100, Tobias Burnus wrote:
> 2023-11-19  Tobias Burnus  <tob...@codesourcery.com>
>           Chung-Lin Tang <clt...@codesourcery.com>
> 
> gcc/ChangeLog:
> 
>       * builtin-types.def (BT_FN_VOID_PTRMODE):
>       (BT_FN_PTRMODE_PTRMODE_INT_PTR): Add.
>       * gimplify.cc (gimplify_bind_expr): Diagnose missing
>       uses_allocators clause.
>       (gimplify_scan_omp_clauses, gimplify_adjust_omp_clauses,
>       gimplify_omp_workshare): Handle uses_allocators.
>       * omp-builtins.def (BUILT_IN_OMP_INIT_ALLOCATOR,
>       BUILT_IN_OMP_DESTROY_ALLOCATOR): Add.
>       * omp-low.cc (scan_sharing_clauses):

Missing description.

> +static tree
> +c_parser_omp_clause_uses_allocators (c_parser *parser, tree list)
> +{
> +  location_t clause_loc = c_parser_peek_token (parser)->location;
> +  tree t = NULL_TREE, nl = list;
> +  matching_parens parens;
> +  if (!parens.require_open (parser))
> +    return list;
> +
> +  tree memspace_expr = NULL_TREE;
> +  tree traits_var = NULL_TREE;
> +
> +  struct item_tok
> +  {
> +    location_t loc;
> +    tree id;
> +    item_tok (void) : loc (UNKNOWN_LOCATION), id (NULL_TREE) {}
> +  };
> +  struct item { item_tok name, arg; };
> +  auto_vec<item> *modifiers = NULL, *allocators = NULL;
> +  auto_vec<item> *cur_list = new auto_vec<item> (4);

This is certainly the first time I've seen pointers to auto_vec,
normally one uses just vec in such cases, auto_vec is used typically
on automatic variables to make sure the destruction is done.

But I think all the first parse it as a token soup without checking
anything and only in the second round actually check it is something
we've never done before in exactly the same situations.

The usual way would be to quickly peek at tokens to see if there is
: ahead and decide based on that.

See e.g. c_parser_omp_clause_allocate clause.

That has_modifiers check could be basically copied over with
the names of modifiers changed, or could be done in a loop, or
could be moved into a helper function which could be used
by c_parser_omp_clause_allocate and this function and perhaps
others, pass it the list of modifiers and have it return whether
there are modifiers or not.
c_parser_omp_clause_linear does this too (though, that has one of
the modifiers without arguments).

I'm afraid if parsing of every clause does the parsing so significantly
differently it will be a maintainance nightmare.

> @@ -23648,7 +23861,8 @@ c_parser_omp_target_exit_data (location_t loc, 
> c_parser *parser,
>       | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_IN_REDUCTION) \
>       | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_THREAD_LIMIT) \
>       | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_IS_DEVICE_PTR)\
> -     | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_HAS_DEVICE_ADDR))
> +     | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_HAS_DEVICE_ADDR)\

Space before \ please (and also  in the line above.

> +           if (strcmp (IDENTIFIER_POINTER (DECL_NAME (t)),
> +                       "omp_null_allocator") == 0)
> +             {
> +               error_at (OMP_CLAUSE_LOCATION (c),
> +                         "%<omp_null_allocator%> cannot be used in "
> +                         "%<uses_allocators%> clause");
> +               break;
> +             }
> +
> +           if (OMP_CLAUSE_USES_ALLOCATORS_MEMSPACE (c)
> +               || OMP_CLAUSE_USES_ALLOCATORS_TRAITS (c))
> +             {
> +               error_at (OMP_CLAUSE_LOCATION (c),
> +                         "modifiers cannot be used with pre-defined "
> +                         "allocators");
> +               break;
> +             }
> +         }
> +       t = OMP_CLAUSE_USES_ALLOCATORS_MEMSPACE (c);
> +       if (t != NULL_TREE
> +           && (TREE_CODE (t) != CONST_DECL
> +               || TREE_CODE (TREE_TYPE (t)) != ENUMERAL_TYPE
> +               || strcmp (IDENTIFIER_POINTER (TYPE_IDENTIFIER (TREE_TYPE 
> (t))),
> +                          "omp_memspace_handle_t") != 0))
> +         {
> +           error_at (OMP_CLAUSE_LOCATION (c), "memspace modifier must be "

Maybe %<memspace%> ?

> +                     "constant enum of %<omp_memspace_handle_t%> type");
> +           remove = true;
> +           break;
> +         }
> +       t = OMP_CLAUSE_USES_ALLOCATORS_TRAITS (c);
> +       if (t != NULL_TREE)
> +         {
> +           bool type_err = false;
> +
> +           if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE
> +               || DECL_SIZE (t) == NULL_TREE)
> +             type_err = true;
> +           else
> +             {
> +               tree elem_t = TREE_TYPE (TREE_TYPE (t));
> +               if (TREE_CODE (elem_t) != RECORD_TYPE
> +                   || strcmp (IDENTIFIER_POINTER (TYPE_IDENTIFIER (elem_t)),
> +                              "omp_alloctrait_t") != 0
> +                   || !TYPE_READONLY (elem_t))
> +                 type_err = true;
> +             }
> +           if (type_err)
> +             {
> +               if (TREE_CODE (t) != ERROR_MARK)

t != error_mark_node

> +                 error_at (OMP_CLAUSE_LOCATION (c), "traits array %qE must "
> +                           "be of %<const omp_alloctrait_t []%> type", t);
> +               else
> +                 error_at (OMP_CLAUSE_LOCATION (c), "traits array must "
> +                           "be of %<const omp_alloctrait_t []%> type");
> +               remove = true;

> +               tree cst_val = decl_constant_value_1 (t, true);
> +               if (cst_val == t)
> +                 {
> +                   error_at (OMP_CLAUSE_LOCATION (c), "traits array must be "
> +                             "of constant values");

must be initialized with constants?

> +  struct item_tok
> +  {
> +    location_t loc;
> +    tree id;
> +    item_tok (void) : loc (UNKNOWN_LOCATION), id (NULL_TREE) {}
> +  };
> +  struct item { item_tok name, arg; };
> +  auto_vec<item> *modifiers = NULL, *allocators = NULL;
> +  auto_vec<item> *cur_list = new auto_vec<item> (4);

Same as what I wrote in C.

> +     case OMP_CLAUSE_USES_ALLOCATORS:
> +       t = OMP_CLAUSE_USES_ALLOCATORS_ALLOCATOR (c);
> +       if (TREE_CODE (t) == FIELD_DECL)
> +         {
> +           sorry_at (OMP_CLAUSE_LOCATION (c), "class members not yet "
> +                     "supported in %<uses_allocators%> clause");
> +           remove = true;
> +           break;
> +         }
> +       t = convert_from_reference (t);
> +       if (TREE_CODE (TREE_TYPE (t)) != ENUMERAL_TYPE
> +           || strcmp (IDENTIFIER_POINTER (TYPE_IDENTIFIER (TREE_TYPE (t))),
> +                      "omp_allocator_handle_t") != 0)

Back to what I wrote about the C++ allocators, if processing_template_decl t
can be type or value dependent and I think we shouldn't stay in the way to
it.
Type dependent as in:
template <typename T, T X>
void foo (T x)
{
  #pragma omp target uses_allocators (memspace (...), traits (...): x)
  ;
  #pragma omp target uses_allocators (X)
  ;
}
instantiated with foo <omp_allocator_handle_t, omp_low_lat_mem_alloc> (...)
or so.  So the usual drill, if it is type dependent, defer checking the
type for later.  If it is value dependent, don't check exact value and
defer for later.

The above also brings the admittedly preexisting issue, do we want to
support the case where the allocator has type oaht from
typedef omp_allocator_handle_t oaht;
or not?  I guess the wording in the standard is unclear on that, users
would certainly expect typedefs/using to work as well (so then
it should be TYPE_MAIN_VARIANT), but perhaps reading OpenMP pedantically
expression of type XYZ is just that type and nothing else.

> +         {
> +           error_at (OMP_CLAUSE_LOCATION (c),
> +                     "allocator must be of %<omp_allocator_handle_t%> type");
> +           remove = true;
> +           break;
> +         }
> +       if (TREE_CODE (t) == CONST_DECL)

This is another question (also for C, at least C23).
While the allocator needs to be an identifier, so the enumerator or some
variable, not some random other expressions (so e.g. calls to
constexpr/consteval functions are not ok), I wonder whether
constexpr omp_allocator_handle_t myh = omp_low_lat_mem_alloc;
... uses_allocator (myh) ...
is ok or not and ditto the above mentioned uses_allocator (X).
I bet in such case one might not get a CONST_DECL here, and instead if
not value dependent should fold_non_dependent_expr and check if it yields
an INTEGER_CST.

> +         {
> +           /* Currently for pre-defined allocators in libgomp, we do not
> +              require additional init/fini inside target regions, so discard
> +              such clauses.  */
> +           remove = true;
> +
> +           if (strcmp (IDENTIFIER_POINTER (DECL_NAME (t)),
> +                       "omp_null_allocator") == 0)

For omp_null_allocator it would then be integer_zerop check.
> +             {
> +               error_at (OMP_CLAUSE_LOCATION (c),
> +                         "%<omp_null_allocator%> cannot be used in "
> +                         "%<uses_allocators%> clause");
> +               break;
> +             }
> +
> +           if (OMP_CLAUSE_USES_ALLOCATORS_MEMSPACE (c)
> +               || OMP_CLAUSE_USES_ALLOCATORS_TRAITS (c))
> +             {
> +               error_at (OMP_CLAUSE_LOCATION (c),
> +                         "modifiers cannot be used with pre-defined "
> +                         "allocators");
> +               break;
> +             }
> +         }
> +       t = OMP_CLAUSE_USES_ALLOCATORS_MEMSPACE (c);
> +       if (t != NULL_TREE
> +           && (TREE_CODE (t) != CONST_DECL
> +               || TREE_CODE (TREE_TYPE (t)) != ENUMERAL_TYPE
> +               || strcmp (IDENTIFIER_POINTER (TYPE_IDENTIFIER (TREE_TYPE 
> (t))),
> +                          "omp_memspace_handle_t") != 0))
> +         {
> +           error_at (OMP_CLAUSE_LOCATION (c), "memspace modifier must be "
> +                     "constant enum of %<omp_memspace_handle_t%> type");

I find this OpenMP standard wording just weird but perhaps it is just
because I'm not a native English speaker.  I'd write with instead of of.

> +           if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE
> +               || DECL_SIZE (t) == NULL_TREE)

|| !COMPLETE_TYPE_P (t)
?
> +             type_err = true;
> +           else
> +             {
> +               tree elem_t = TREE_TYPE (TREE_TYPE (t));
> +               if (TREE_CODE (elem_t) != RECORD_TYPE
> +                   || strcmp (IDENTIFIER_POINTER (TYPE_IDENTIFIER (elem_t)),
> +                              "omp_alloctrait_t") != 0

Again, question is about typedefs.

> +       for (tree *cp = &OMP_CLAUSES (expr); *cp != NULL;
> +            cp = &OMP_CLAUSE_CHAIN (*cp))
> +         if (OMP_CLAUSE_CODE (*cp) == OMP_CLAUSE_USES_ALLOCATORS)
> +           {
> +             tree c = *cp;
> +             tree allocator = OMP_CLAUSE_USES_ALLOCATORS_ALLOCATOR (c);
> +             tree memspace = OMP_CLAUSE_USES_ALLOCATORS_MEMSPACE (c);
> +             tree traits = OMP_CLAUSE_USES_ALLOCATORS_TRAITS (c);
> +
> +             if (omp_init_allocator_fn == NULL_TREE)
> +               {
> +                 omp_init_allocator_fn
> +                   = builtin_decl_explicit (BUILT_IN_OMP_INIT_ALLOCATOR);
> +                 omp_destroy_allocator_fn
> +                   = builtin_decl_explicit (BUILT_IN_OMP_DESTROY_ALLOCATOR);
> +               }

What is unclear to me is how this is supposed to behave in target teams
case.  Whether it is acceptable that each team calls omp_init_allocator
and each team has a separate value for the privatized var, or if we'd need
to arrange for it to happen just once and be propagated to other teams
somehow (but that is perhaps impossible).

> +             tree ntraits, traits_var;
> +             if (traits == NULL_TREE)
> +               {
> +                  ntraits = integer_zero_node;
> +                  traits_var = null_pointer_node;
> +               }
> +             else if (DECL_INITIAL (traits))
> +               {
> +                 location_t loc = OMP_CLAUSE_LOCATION (c);
> +                 tree t = DECL_INITIAL (traits);
> +                 gcc_assert (TREE_CODE (t) == CONSTRUCTOR);
> +                 ntraits = build_int_cst (integer_type_node,
> +                                          CONSTRUCTOR_NELTS (t));

We certainly shouldn't derive ntraits from whether it is a CONSTRUCTOR
and how many CONSTRUCTOR_NELTS it has, it could have fewer than the
array size.  IMHO we should go with the array_type_nelts in both cases
(and complain perhaps already in the FEs if it is not constant, we don't
want VLAs there I suppose).  Perhaps repeat the CONSTRUCTOR check
for DECL_INITIAL as well earlier when you decide if it should be
firstprivatized or not.

> +                 t = get_initialized_tmp_var (t, &init_seq, NULL);
> +                 traits_var = build_fold_addr_expr_loc (loc, t);
> +               }
> +             else
> +               {
> +                 location_t loc = OMP_CLAUSE_LOCATION (c);
> +                 gcc_assert (TREE_CODE (TREE_TYPE (traits)) == ARRAY_TYPE);
> +                 tree t = TYPE_DOMAIN (TREE_TYPE (traits));
> +                 tree min = TYPE_MIN_VALUE (t);
> +                 tree max = TYPE_MAX_VALUE (t);
> +                 gcc_assert (TREE_CODE (min) == INTEGER_CST
> +                             && TREE_CODE (max) == INTEGER_CST);
> +                 t = fold_build2_loc (loc, MINUS_EXPR, TREE_TYPE (min),
> +                                      max, min);
> +                 t = fold_build2_loc (loc, PLUS_EXPR, TREE_TYPE (min),
> +                                      t, build_int_cst (TREE_TYPE (min), 1));
> +                 ntraits = t;

Use array_type_nelts.

        Jakub

Reply via email to