diff options
author | Jakub Jelinek <jakub@redhat.com> | 2023-11-04 09:09:08 +0100 |
---|---|---|
committer | Jakub Jelinek <jakub@redhat.com> | 2023-11-04 09:09:08 +0100 |
commit | 40b9af020fc2842b4e22ae06742088815400631e (patch) | |
tree | be3a513ca58aad599180e0752e46cb4412cc7217 /gcc/c/c-parser.cc | |
parent | 8d22ac6a18cf542cd541c06b2a7df8fdd293946d (diff) | |
download | gcc-40b9af020fc2842b4e22ae06742088815400631e.zip gcc-40b9af020fc2842b4e22ae06742088815400631e.tar.gz gcc-40b9af020fc2842b4e22ae06742088815400631e.tar.bz2 |
openmp: Add support for omp::directive and omp::sequence attributes in C2X
The following patch adds support for attribute syntax which we have in C++11
and above since GCC 12 also for C, where OpenMP standard is going to add it
in OpenMP 6.0.
2023-11-04 Jakub Jelinek <jakub@redhat.com>
gcc/c/
* c-tree.def: New file.
* c-tree.h (struct c_tree_token_vec): Forward declare.
(c_tree_size): Declare.
* c-lang.h (struct c_omp_declare_target_attr): Add attr_syntax member.
(struct c_omp_begin_assumes_data): New type.
(current_omp_begin_assumes): Change type from int to
vec<c_omp_begin_assumes_data, va_gc> *.
* c-lang.cc: Include c-family/c-pragma.h and c-parser.h.
* c-parser.h (struct c_tree_token_vec_struct): New type.
(C_TOKEN_VEC_TOKENS): New macro.
* c-parser.cc (struct c_parser): Add omp_attrs_forbidden_p and
in_omp_attribute_pragma members.
(c_parser_skip_until_found): Handle CPP_PRAGMA_EOL when
parser->in_omp_attribute_pragma.
(c_parser_skip_to_pragma_eol): Likewise.
(c_parser_translation_unit): Adjust for current_omp_begin_assumes
being a vector rather than counter.
(c_parser_declaration_or_fndef): Handle omp::directive and
omp::sequence attributes on attribute declaration and declare simd
or declare variant directives in those on function declarations.
(c_parser_check_balanced_raw_token_sequence): Forward declare.
(c_parser_omp_directive_args, c_parser_omp_sequence_args): New
functions.
(c_parser_std_attribute): Handle omp::directive and omp::sequence
attributes.
(struct c_omp_attribute_data): New type.
(c_parser_handle_statement_omp_attributes,
c_parser_handle_directive_omp_attributes): New functions.
(c_parser_compound_statement_nostart): Handle omp::directive and
omp::sequence attributes on statements. Formatting fix.
(c_parser_all_labels): Handle omp::directive and omp::sequence
attributes on statements.
(c_parser_statement): Clear parser->omp_attrs_forbidden_p.
(c_parser_omp_variable_list): Handle parser->tokens
!= &parser->tokens_buf[0] by saving/restoring it.
(c_parser_omp_structured_block): Set parser->omp_attrs_forbidden_p.
(c_parser_omp_section_scan): New function.
(c_parser_omp_structured_block_sequence, c_parser_omp_sections_scope):
Use it.
(c_parser_omp_parallel): Set parser->omp_attrs_forbidden_p.
(c_parser_omp_task): Likewise.
(c_parser_omp_declare_simd): Handle function declaration after
std attributes.
(c_finish_omp_declare_simd): Don't assert all kinds are the same.
(c_parser_omp_declare_target): Also push attr_syntax flag.
(c_parser_omp_begin): Likewise. Adjust for current_omp_begin_assumes
type change.
(c_parser_omp_end): Adjust for current_omp_begin_assumes type
change. Diagnose mixing of attribute vs. pragma syntax on end assumes
or end declare target.
(c_parser_omp_declare_reduction): Handle parser->tokens
!= &parser->tokens_buf[0] by saving/restoring it.
* c-decl.cc: Include c-parser.h.
(current_omp_begin_assumes): Change type from int to
vec<c_omp_begin_assumes_data, va_gc> *.
(struct c_tree_token_vec): New type. Add static assertions
for sizeof and offsetof.
(union lang_tree_node): Add c_token_vec member and adjust GTY
desc for it.
(c_tree_size): New function.
(c_decl_attributes): Diagnose invalid omp::directive attribute
uses.
* c-objc-common.h (LANG_HOOKS_TREE_SIZE): Redefine.
gcc/cp/
* parser.h (struct cp_parser): Adjust comment on omp_attrs_forbidden_p
member.
* parser.cc (cp_parser_omp_section_scan): Allow __directive__ spelling.
gcc/objc/
* objc-act.h (objc_common_tree_size): Remove.
* objc-act.cc (objc_common_tree_size): Remove.
* objc-lang.cc (LANG_HOOKS_TREE_SIZE): Remove.
gcc/testsuite/
* gcc.dg/gomp/attrs-1.c: New test.
* gcc.dg/gomp/attrs-2.c: New test.
* gcc.dg/gomp/attrs-3.c: New test.
* gcc.dg/gomp/attrs-4.c: New test.
* gcc.dg/gomp/attrs-5.c: New test.
* gcc.dg/gomp/attrs-6.c: New test.
* gcc.dg/gomp/attrs-7.c: New test.
* gcc.dg/gomp/attrs-8.c: New test.
* gcc.dg/gomp/attrs-9.c: New test.
* gcc.dg/gomp/attrs-10.c: New test.
* gcc.dg/gomp/attrs-11.c: New test.
* gcc.dg/gomp/attrs-12.c: New test.
* gcc.dg/gomp/attrs-13.c: New test.
* gcc.dg/gomp/attrs-14.c: New test.
* gcc.dg/gomp/attrs-15.c: New test.
* gcc.dg/gomp/attrs-16.c: New test.
* gcc.dg/gomp/attrs-17.c: New test.
* gcc.dg/gomp/attrs-18.c: New test.
* g++.dg/gomp/attrs-2.C: Enable for c++11 rather than just
c++17. Avoid using omp : syntax for c++11, c++14 and c.
Diffstat (limited to 'gcc/c/c-parser.cc')
-rw-r--r-- | gcc/c/c-parser.cc | 840 |
1 files changed, 799 insertions, 41 deletions
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc index 5213a57..20efd4d 100644 --- a/gcc/c/c-parser.cc +++ b/gcc/c/c-parser.cc @@ -247,12 +247,21 @@ struct GTY(()) c_parser { macro. */ BOOL_BITFIELD seen_string_literal : 1; + /* TRUE if omp::directive, omp::decl or omp::sequence attributes may not + appear. */ + BOOL_BITFIELD omp_attrs_forbidden_p : 1; + /* Location of the last consumed token. */ location_t last_token_location; /* Holds state for parsing collapsed OMP_FOR loops. Managed by c_parser_omp_for_loop. */ struct omp_for_parse_data * GTY((skip)) omp_for_parse_state; + + /* If we're in the context of OpenMP directives written as C23 + attributes turned into pragma, vector of tokens created from that, + otherwise NULL. */ + vec<c_token, va_gc> *in_omp_attribute_pragma; }; /* Return a pointer to the Nth token in PARSERs tokens_buf. */ @@ -1288,7 +1297,19 @@ c_parser_skip_until_found (c_parser *parser, unsigned nesting_depth = 0; if (c_parser_require (parser, type, msgid, matching_location)) - return; + { + if (UNLIKELY (type == CPP_PRAGMA_EOL) && parser->in_omp_attribute_pragma) + { + c_token *token = c_parser_peek_token (parser); + if (token->type == CPP_EOF) + { + parser->tokens = &parser->tokens_buf[0]; + parser->tokens_avail = token->flags; + parser->in_omp_attribute_pragma = NULL; + } + } + return; + } /* Skip tokens until the desired token is found. */ while (true) @@ -1299,6 +1320,17 @@ c_parser_skip_until_found (c_parser *parser, if (token->type == type && !nesting_depth) { c_parser_consume_token (parser); + if (UNLIKELY (type == CPP_PRAGMA_EOL) + && parser->in_omp_attribute_pragma) + { + c_token *token = c_parser_peek_token (parser); + if (token->type == CPP_EOF) + { + parser->tokens = &parser->tokens_buf[0]; + parser->tokens_avail = token->flags; + parser->in_omp_attribute_pragma = NULL; + } + } break; } @@ -1383,6 +1415,17 @@ c_parser_skip_to_pragma_eol (c_parser *parser, bool error_if_not_eol = true) } while (token_type != CPP_PRAGMA_EOL); + if (parser->in_omp_attribute_pragma) + { + c_token *token = c_parser_peek_token (parser); + if (token->type == CPP_EOF) + { + parser->tokens = &parser->tokens_buf[0]; + parser->tokens_avail = token->flags; + parser->in_omp_attribute_pragma = NULL; + } + } + parser->error = false; } @@ -1581,6 +1624,8 @@ static void c_parser_declaration_or_fndef (c_parser *, bool, bool, bool, tree attrs = NULL, struct oacc_routine_data * = NULL, bool * = NULL); +static bool c_parser_handle_statement_omp_attributes (c_parser *, tree &, + bool *); static void c_parser_static_assert_declaration_no_semi (c_parser *); static void c_parser_static_assert_declaration (c_parser *); static struct c_typespec c_parser_enum_specifier (c_parser *); @@ -1871,12 +1916,12 @@ c_parser_translation_unit (c_parser *parser) "#pragma omp end declare target"); vec_safe_truncate (current_omp_declare_target_attribute, 0); } - if (current_omp_begin_assumes) + if (vec_safe_length (current_omp_begin_assumes)) { if (!errorcount) error ("%qs without corresponding %qs", "#pragma omp begin assumes", "#pragma omp end assumes"); - current_omp_begin_assumes = 0; + vec_safe_truncate (current_omp_begin_assumes, 0); } #if ENABLE_ANALYZER @@ -1999,6 +2044,8 @@ c_parser_external_declaration (c_parser *parser) } } +static void c_parser_handle_directive_omp_attributes (tree &, vec<c_token> *&, + vec<c_token> *); static void c_finish_omp_declare_simd (c_parser *, tree, tree, vec<c_token> *); static void c_finish_oacc_routine (struct oacc_routine_data *, tree, bool); @@ -2301,6 +2348,22 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, if (c_parser_next_token_is (parser, CPP_SEMICOLON)) { bool handled_assume = false; + if (specs->attrs + && !nested + && specs->typespec_kind == ctsk_none + && c_parser_handle_statement_omp_attributes (parser, specs->attrs, + NULL)) + { + if (specs->attrs) + c_warn_unused_attributes (specs->attrs); + while (parser->in_omp_attribute_pragma) + { + gcc_assert (c_parser_next_token_is (parser, CPP_PRAGMA)); + c_parser_pragma (parser, pragma_external, NULL); + } + c_parser_consume_token (parser); + return; + } if (specs->typespec_kind == ctsk_none && lookup_attribute ("gnu", "assume", specs->attrs)) { @@ -2440,6 +2503,10 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, warning_at (here, OPT_Wattributes, "%<assume%> attribute not followed by %<;%>"); + auto_vec<c_token> omp_declare_simd_attr_clauses; + c_parser_handle_directive_omp_attributes (specs->attrs, + omp_declare_simd_clauses, + &omp_declare_simd_attr_clauses); pending_xref_error (); prefix_attrs = specs->attrs; all_prefix_attrs = prefix_attrs; @@ -2451,6 +2518,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, timevar_id_t tv; tree fnbody = NULL_TREE; tree underspec_name = NULL_TREE; + auto_vec<c_token> omp_dsimd_idattr_clauses; /* Declaring either one or more declarators (in which case we should diagnose if there were no declaration specifiers) or a function definition (in which case the diagnostic for @@ -2468,6 +2536,15 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, c_parser_skip_to_end_of_block_or_statement (parser); return; } + if (flag_openmp || flag_openmp_simd) + { + struct c_declarator *d = declarator; + while (d->kind != cdk_id) + d = d->declarator; + vec<c_token> *dummy = NULL; + c_parser_handle_directive_omp_attributes (d->u.id.attrs, dummy, + &omp_dsimd_idattr_clauses); + } if (gnu_auto_type_p && declarator->kind != cdk_id) { error_at (here, @@ -2607,6 +2684,9 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, if (omp_declare_simd_clauses) c_finish_omp_declare_simd (parser, d, NULL_TREE, omp_declare_simd_clauses); + if (!omp_dsimd_idattr_clauses.is_empty ()) + c_finish_omp_declare_simd (parser, d, NULL_TREE, + &omp_dsimd_idattr_clauses); } else { @@ -2628,6 +2708,10 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, if (!specs->constexpr_p && omp_declare_simd_clauses) c_finish_omp_declare_simd (parser, d, NULL_TREE, omp_declare_simd_clauses); + if (!specs->constexpr_p + && !omp_dsimd_idattr_clauses.is_empty ()) + c_finish_omp_declare_simd (parser, d, NULL_TREE, + &omp_dsimd_idattr_clauses); start_init (d, asm_name, TREE_STATIC (d) || specs->constexpr_p, specs->constexpr_p, &richloc); @@ -2646,6 +2730,10 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, if (omp_declare_simd_clauses) c_finish_omp_declare_simd (parser, d, NULL_TREE, omp_declare_simd_clauses); + if (!specs->constexpr_p + && !omp_dsimd_idattr_clauses.is_empty ()) + c_finish_omp_declare_simd (parser, d, NULL_TREE, + &omp_dsimd_idattr_clauses); } finish_init (); } @@ -2715,7 +2803,8 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, warn_parm_array_mismatch (lastloc, d, parms); } } - if (omp_declare_simd_clauses) + if (omp_declare_simd_clauses + || !omp_dsimd_idattr_clauses.is_empty ()) { tree parms = NULL_TREE; if (d && TREE_CODE (d) == FUNCTION_DECL) @@ -2732,8 +2821,13 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, } if (parms) temp_store_parm_decls (d, parms); - c_finish_omp_declare_simd (parser, d, parms, - omp_declare_simd_clauses); + if (omp_declare_simd_clauses) + c_finish_omp_declare_simd (parser, d, parms, + omp_declare_simd_clauses); + if (!specs->constexpr_p + && !omp_dsimd_idattr_clauses.is_empty ()) + c_finish_omp_declare_simd (parser, d, parms, + &omp_dsimd_idattr_clauses); if (parms) temp_pop_parm_decls (); } @@ -2878,6 +2972,9 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, if (omp_declare_simd_clauses) c_finish_omp_declare_simd (parser, current_function_decl, NULL_TREE, omp_declare_simd_clauses); + if (!omp_dsimd_idattr_clauses.is_empty ()) + c_finish_omp_declare_simd (parser, current_function_decl, NULL_TREE, + &omp_dsimd_idattr_clauses); if (oacc_routine_data) c_finish_oacc_routine (oacc_routine_data, current_function_decl, true); location_t startloc = c_parser_peek_token (parser)->location; @@ -5430,6 +5527,134 @@ c_parser_balanced_token_sequence (c_parser *parser) } } +static bool c_parser_check_balanced_raw_token_sequence (c_parser *, + unsigned int *); + +/* Parse arguments of omp::directive or omp::decl attribute. + + directive-name ,[opt] clause-list[opt] + + For directive just remember the tokens in a vector for subsequent + parsing. */ + +static void +c_parser_omp_directive_args (c_parser *parser, tree attribute, bool decl_p) +{ + unsigned int n = 1; + c_token *first = c_parser_peek_token (parser); + if (!c_parser_check_balanced_raw_token_sequence (parser, &n) + || (c_parser_peek_nth_token_raw (parser, n)->type + != CPP_CLOSE_PAREN)) + { + c_parser_balanced_token_sequence (parser); + TREE_VALUE (attribute) = NULL_TREE; + return; + } + if (n == 1) + { + error_at (first->location, "expected OpenMP directive name"); + TREE_VALUE (attribute) = NULL_TREE; + return; + } + vec<c_token, va_gc> *v; + vec_alloc (v, n - 1); + for (--n; n; --n) + { + c_token *tok = c_parser_peek_token (parser); + v->quick_push (*tok); + c_parser_consume_token (parser); + } + tree arg = make_node (C_TOKEN_VEC); + C_TOKEN_VEC_TOKENS (arg) = v; + if (decl_p) + TREE_PUBLIC (arg) = 1; + TREE_VALUE (attribute) = tree_cons (NULL_TREE, arg, TREE_VALUE (attribute)); +} + +/* Parse arguments of omp::sequence attribute. + + omp::[opt] directive-attr [ , omp::[opt] directive-attr ]... */ + +static void +c_parser_omp_sequence_args (c_parser *parser, tree attribute) +{ + do + { + c_token *token = c_parser_peek_token (parser); + if (token->type == CPP_NAME + && strcmp (IDENTIFIER_POINTER (token->value), "omp") == 0 + && c_parser_peek_2nd_token (parser)->type == CPP_SCOPE) + { + c_parser_consume_token (parser); + c_parser_consume_token (parser); + token = c_parser_peek_token (parser); + } + bool directive = false; + const char *p; + if (token->type != CPP_NAME) + p = ""; + else + p = IDENTIFIER_POINTER (token->value); + if (strcmp (p, "directive") == 0) + directive = true; + else if (strcmp (p, "sequence") != 0) + { + error_at (token->location, "expected %<directive%> or %<sequence%>"); + unsigned nesting_depth = 0; + + while (true) + { + /* Peek at the next token. */ + token = c_parser_peek_token (parser); + /* If we've reached the token we want, consume it and stop. */ + if ((token->type == CPP_CLOSE_PAREN || token->type == CPP_COMMA) + && !nesting_depth) + break; + /* If we've run out of tokens, stop. */ + if (token->type == CPP_EOF) + break; + if (token->type == CPP_PRAGMA_EOL && parser->in_pragma) + break; + if (token->type == CPP_OPEN_BRACE + || token->type == CPP_OPEN_PAREN + || token->type == CPP_OPEN_SQUARE) + ++nesting_depth; + else if (token->type == CPP_CLOSE_BRACE + || token->type == CPP_CLOSE_PAREN + || token->type == CPP_CLOSE_SQUARE) + { + if (nesting_depth-- == 0) + break; + } + /* Consume this token. */ + c_parser_consume_token (parser); + } + if (c_parser_next_token_is_not (parser, CPP_COMMA)) + break; + c_parser_consume_token (parser); + continue; + } + c_parser_consume_token (parser); + matching_parens parens; + if (parens.require_open (parser)) + { + if (directive) + c_parser_omp_directive_args (parser, attribute, false); + else + c_parser_omp_sequence_args (parser, attribute); + parens.skip_until_found_close (parser); + if (c_parser_next_token_is_not (parser, CPP_COMMA)) + break; + c_parser_consume_token (parser); + } + else if (c_parser_next_token_is_not (parser, CPP_COMMA)) + break; + else + c_parser_consume_token (parser); + } + while (1); +} + /* Parse standard (C2X) attributes (including GNU attributes in the gnu:: namespace). @@ -5512,7 +5737,19 @@ c_parser_std_attribute (c_parser *parser, bool for_tm, /* Parse the arguments, if any. */ const attribute_spec *as = lookup_attribute_spec (TREE_PURPOSE (attribute)); if (c_parser_next_token_is_not (parser, CPP_OPEN_PAREN)) - goto out; + { + if ((flag_openmp || flag_openmp_simd) + && ns + && is_attribute_p ("omp", ns) + && (is_attribute_p ("directive", name) + || is_attribute_p ("sequence", name) + || is_attribute_p ("decl", name))) + { + error ("%<omp::%E%> attribute requires argument", name); + return error_mark_node; + } + goto out; + } { location_t open_loc = c_parser_peek_token (parser)->location; matching_parens parens; @@ -5549,7 +5786,37 @@ c_parser_std_attribute (c_parser *parser, bool for_tm, require_string, assume_attr, false); } else - c_parser_balanced_token_sequence (parser); + { + if ((flag_openmp || flag_openmp_simd) + && ns + && is_attribute_p ("omp", ns)) + { + if (is_attribute_p ("directive", name)) + { + c_parser_omp_directive_args (parser, attribute, false); + parens.skip_until_found_close (parser); + return attribute; + } +/* else if (is_attribute_p ("decl", name)) + { + TREE_VALUE (TREE_PURPOSE (attribute)) + = get_identifier ("directive"); + c_parser_omp_directive_args (parser, attribute, true); + parens.skip_until_found_close (parser); + return attribute; + } */ + else if (is_attribute_p ("sequence", name)) + { + TREE_VALUE (TREE_PURPOSE (attribute)) + = get_identifier ("directive"); + c_parser_omp_sequence_args (parser, attribute); + parens.skip_until_found_close (parser); + TREE_VALUE (attribute) = nreverse (TREE_VALUE (attribute)); + return attribute; + } + } + c_parser_balanced_token_sequence (parser); + } parens.require_close (parser); } out: @@ -6322,6 +6589,350 @@ add_structured_block_stmt (tree sl) add_stmt (build1 (OMP_STRUCTURED_BLOCK, void_type_node, sl)); } +struct c_omp_attribute_data +{ + vec<c_token, va_gc> *tokens; + const c_omp_directive *dir; + c_omp_directive_kind kind; +}; + +/* Handle omp::directive and omp::sequence attributes in ATTRS + (if any) at the start of a statement or in attribute-declaration. */ + +static bool +c_parser_handle_statement_omp_attributes (c_parser *parser, tree &attrs, + bool *have_std_attrs) +{ + if (!flag_openmp && !flag_openmp_simd) + return false; + + auto_vec<c_omp_attribute_data, 16> vd; + int cnt = 0; + int tokens = 0; + bool bad = false; + for (tree *pa = &attrs; *pa; ) + if (is_attribute_namespace_p ("omp", *pa) + && is_attribute_p ("directive", get_attribute_name (*pa))) + { + cnt++; + for (tree a = TREE_VALUE (*pa); a; a = TREE_CHAIN (a)) + { + tree d = TREE_VALUE (a); + gcc_assert (TREE_CODE (d) == C_TOKEN_VEC); + vec<c_token, va_gc> *toks = C_TOKEN_VEC_TOKENS (d); + c_token *first = toks->address (); + c_token *last = first + toks->length (); + if (parser->omp_attrs_forbidden_p) + { + error_at (first->location, + "mixing OpenMP directives with attribute and pragma " + "syntax on the same statement"); + parser->omp_attrs_forbidden_p = false; + bad = true; + } + else if (TREE_PUBLIC (d)) + { + error_at (first->location, + "OpenMP %<omp::decl%> attribute on a statement"); + bad = true; + } + const char *directive[3] = {}; + for (int i = 0; i < 3; i++) + { + tree id = NULL_TREE; + if (first + i == last) + break; + if (first[i].type == CPP_NAME) + id = first[i].value; + else if (first[i].type == CPP_KEYWORD) + id = ridpointers[(int) first[i].keyword]; + else + break; + directive[i] = IDENTIFIER_POINTER (id); + } + const c_omp_directive *dir = NULL; + if (directive[0]) + dir = c_omp_categorize_directive (directive[0], directive[1], + directive[2]); + if (dir == NULL) + { + error_at (first->location, + "unknown OpenMP directive name in %qs attribute " + "argument", + TREE_PUBLIC (d) ? "omp::decl" : "omp::directive"); + continue; + } + c_omp_directive_kind kind = dir->kind; + if (dir->id == PRAGMA_OMP_ORDERED) + { + /* ordered is C_OMP_DIR_CONSTRUCT only if it doesn't contain + depend/doacross clause. */ + if (directive[1] + && (strcmp (directive[1], "depend") == 0 + || strcmp (directive[1], "doacross") == 0)) + kind = C_OMP_DIR_STANDALONE; + else if (first + 2 < last + && first[1].type == CPP_COMMA + && first[2].type == CPP_NAME + && (strcmp (IDENTIFIER_POINTER (first[2].value), + "depend") == 0 + || strcmp (IDENTIFIER_POINTER (first[2].value), + "doacross") == 0)) + kind = C_OMP_DIR_STANDALONE; + } + else if (dir->id == PRAGMA_OMP_ERROR) + { + /* error with at(execution) clause is C_OMP_DIR_STANDALONE. */ + int paren_depth = 0; + for (int i = 1; first + i < last; i++) + if (first[i].type == CPP_OPEN_PAREN) + paren_depth++; + else if (first[i].type == CPP_CLOSE_PAREN) + paren_depth--; + else if (paren_depth == 0 + && first + i + 2 < last + && first[i].type == CPP_NAME + && first[i + 1].type == CPP_OPEN_PAREN + && first[i + 2].type == CPP_NAME + && !strcmp (IDENTIFIER_POINTER (first[i].value), + "at") + && !strcmp (IDENTIFIER_POINTER (first[i + + 2].value), + "execution")) + { + kind = C_OMP_DIR_STANDALONE; + break; + } + } + c_omp_attribute_data v = { toks, dir, kind }; + vd.safe_push (v); + if (flag_openmp || dir->simd) + tokens += (last - first) + 1; + } + c_omp_attribute_data v = {}; + vd.safe_push (v); + *pa = TREE_CHAIN (*pa); + } + else + pa = &TREE_CHAIN (*pa); + + if (bad) + { + fail: + if (have_std_attrs && attrs == NULL) + *have_std_attrs = false; + return false; + } + + unsigned int i; + c_omp_attribute_data *v; + c_omp_attribute_data *construct_seen = nullptr; + c_omp_attribute_data *standalone_seen = nullptr; + c_omp_attribute_data *prev_standalone_seen = nullptr; + FOR_EACH_VEC_ELT (vd, i, v) + if (v->tokens) + { + if (v->kind == C_OMP_DIR_CONSTRUCT && !construct_seen) + construct_seen = v; + else if (v->kind == C_OMP_DIR_STANDALONE && !standalone_seen) + standalone_seen = v; + } + else + { + if (standalone_seen && !prev_standalone_seen) + { + prev_standalone_seen = standalone_seen; + standalone_seen = nullptr; + } + } + + if (cnt > 1 && construct_seen) + { + error_at ((*construct_seen->tokens)[0].location, + "OpenMP construct among %<omp::directive%> attributes" + " requires all %<omp::directive%> attributes on the" + " same statement to be in the same %<omp::sequence%>"); + goto fail; + } + if (cnt > 1 && standalone_seen && prev_standalone_seen) + { + error_at ((*standalone_seen->tokens)[0].location, + "multiple OpenMP standalone directives among" + " %<omp::directive%> attributes must be all within the" + " same %<omp::sequence%>"); + goto fail; + } + + if (prev_standalone_seen) + standalone_seen = prev_standalone_seen; + if (standalone_seen + && !c_parser_next_token_is (parser, CPP_SEMICOLON)) + { + error_at (standalone_seen->tokens->address ()->location, + "standalone OpenMP directives in %<omp::directive%> attribute" + " can only appear on an empty statement"); + goto fail; + } + if (cnt && c_parser_next_token_is (parser, CPP_PRAGMA)) + { + c_token *token = c_parser_peek_token (parser); + enum pragma_kind kind = token->pragma_kind; + if (kind >= PRAGMA_OMP__START_ && kind <= PRAGMA_OMP__LAST_) + { + error_at (token->location, + "mixing OpenMP directives with attribute and pragma " + "syntax on the same statement"); + goto fail; + } + } + + if (!tokens) + return false; + + unsigned int tokens_avail = parser->tokens_avail; + gcc_assert (parser->tokens == &parser->tokens_buf[0]); + + tokens++; + vec<c_token, va_gc> *toks = NULL; + vec_safe_reserve (toks, tokens, true); + FOR_EACH_VEC_ELT (vd, i, v) + { + if (!v->tokens) + continue; + if (!flag_openmp && !v->dir->simd) + continue; + c_token *first = v->tokens->address (); + c_token *last = first + v->tokens->length (); + c_token tok = {}; + tok.type = CPP_PRAGMA; + tok.keyword = RID_MAX; + tok.pragma_kind = pragma_kind (v->dir->id); + tok.location = first->location; + toks->quick_push (tok); + while (++first < last) + toks->quick_push (*first); + tok = {}; + tok.type = CPP_PRAGMA_EOL; + tok.keyword = RID_MAX; + tok.location = last[-1].location; + toks->quick_push (tok); + } + + c_token tok = {}; + tok.type = CPP_EOF; + tok.keyword = RID_MAX; + tok.location = toks->last ().location; + tok.flags = tokens_avail; + toks->quick_push (tok); + + parser->tokens = toks->address (); + parser->tokens_avail = tokens; + parser->in_omp_attribute_pragma = toks; + return true; +} + +/* Handle omp::directive and omp::sequence attributes in ATTRS + (if any) at the start or after declaration-id of a declaration. */ + +static void +c_parser_handle_directive_omp_attributes (tree &attrs, + vec<c_token> *&pragma_clauses, + vec<c_token> *attr_clauses) +{ + if (!flag_openmp && !flag_openmp_simd) + return; + + for (tree *pa = &attrs; *pa; ) + if (is_attribute_namespace_p ("omp", *pa) + && is_attribute_p ("directive", get_attribute_name (*pa))) + { + int cnt = 0; + for (tree *pa2 = &TREE_VALUE (*pa); *pa2; ) + { + tree a = *pa2; + tree d = TREE_VALUE (a); + gcc_assert (TREE_CODE (d) == C_TOKEN_VEC); + vec<c_token, va_gc> *toks = C_TOKEN_VEC_TOKENS (d); + c_token *first = toks->address (); + c_token *last = first + toks->length (); + const char *directive[3] = {}; + for (int i = 0; i < 3; i++) + { + tree id = NULL_TREE; + if (first + i == last) + break; + if (first[i].type == CPP_NAME) + id = first[i].value; + else if (first[i].type == CPP_KEYWORD) + id = ridpointers[(int) first[i].keyword]; + else + break; + directive[i] = IDENTIFIER_POINTER (id); + } + const c_omp_directive *dir = NULL; + if (directive[0]) + dir = c_omp_categorize_directive (directive[0], directive[1], + directive[2]); + if (dir == NULL) + { + error_at (first->location, + "unknown OpenMP directive name in " + "%qs attribute argument", + TREE_PUBLIC (d) ? "omp::decl" : "omp::directive"); + *pa2 = TREE_CHAIN (a); + } + else if (dir->id == PRAGMA_OMP_DECLARE + && (strcmp (directive[1], "simd") == 0 + || strcmp (directive[1], "variant") == 0)) + { + if (pragma_clauses) + { + error_at (first->location, + "mixing OpenMP directives with attribute and " + "pragma syntax on the same declaration"); + for (pa = &attrs; *pa; ) + if (is_attribute_namespace_p ("omp", *pa) + && is_attribute_p ("directive", + get_attribute_name (*pa))) + *pa = TREE_CHAIN (*pa); + else + pa = &TREE_CHAIN (*pa); + return; + } + ++cnt; + attr_clauses->reserve (attr_clauses->length () + + toks->length () + 2); + for (++first; first < last; ++first) + attr_clauses->quick_push (*first); + c_token tok = {}; + tok.type = CPP_PRAGMA_EOL; + tok.keyword = RID_MAX; + tok.location = last[-1].location; + attr_clauses->quick_push (tok); + *pa2 = TREE_CHAIN (a); + } + else + pa2 = &TREE_CHAIN (a); + } + if (cnt && TREE_VALUE (*pa) == NULL_TREE) + *pa = TREE_CHAIN (*pa); + else + pa = &TREE_CHAIN (*pa); + } + else + pa = &TREE_CHAIN (*pa); + if (attr_clauses->length ()) + { + c_token tok = {}; + tok.type = CPP_EOF; + tok.keyword = RID_MAX; + tok.location = attr_clauses->last ().location; + attr_clauses->quick_push (tok); + attr_clauses->quick_push (tok); + pragma_clauses = attr_clauses; + } +} + /* Parse a compound statement except for the opening brace. This is used for parsing both compound statements and statement expressions (which follow different paths to handling the opening). */ @@ -6495,7 +7106,10 @@ c_parser_compound_statement_nostart (c_parser *parser) } else if (c_parser_next_tokens_start_declaration (parser) || (have_std_attrs - && c_parser_next_token_is (parser, CPP_SEMICOLON))) + && !c_parser_handle_statement_omp_attributes + (parser, std_attrs, &have_std_attrs) + && c_parser_next_token_is (parser, CPP_SEMICOLON) + && (have_std_attrs = true))) { if (last_label) pedwarn_c11 (c_parser_peek_token (parser)->location, OPT_Wpedantic, @@ -6569,8 +7183,10 @@ c_parser_compound_statement_nostart (c_parser *parser) } else if (c_parser_next_token_is (parser, CPP_PRAGMA)) { - if (have_std_attrs) + if (have_std_attrs && !parser->in_omp_attribute_pragma) c_parser_error (parser, "expected declaration or statement"); + else if (std_attrs) + c_warn_unused_attributes (std_attrs); /* External pragmas, and some omp pragmas, are not associated with regular c code, and so are not to be considered statements syntactically. This ensures that the user doesn't put them @@ -6588,7 +7204,7 @@ c_parser_compound_statement_nostart (c_parser *parser) check_omp_intervening_code (parser); } if (omp_for_parse_state) - omp_for_parse_state->want_nested_loop = want_nested_loop; + omp_for_parse_state->want_nested_loop = want_nested_loop; } else if (c_parser_next_token_is (parser, CPP_EOF)) { @@ -6666,13 +7282,10 @@ c_parser_compound_statement_nostart (c_parser *parser) static void c_parser_all_labels (c_parser *parser) { + bool have_std_attrs; tree std_attrs = NULL; - if (c_parser_nth_token_starts_std_attributes (parser, 1)) - { - std_attrs = c_parser_std_attribute_specifier_sequence (parser); - if (c_parser_next_token_is (parser, CPP_SEMICOLON)) - c_parser_error (parser, "expected statement"); - } + if ((have_std_attrs = c_parser_nth_token_starts_std_attributes (parser, 1))) + std_attrs = c_parser_std_attribute_specifier_sequence (parser); while (c_parser_next_token_is_keyword (parser, RID_CASE) || c_parser_next_token_is_keyword (parser, RID_DEFAULT) || (c_parser_next_token_is (parser, CPP_NAME) @@ -6680,15 +7293,20 @@ c_parser_all_labels (c_parser *parser) { c_parser_label (parser, std_attrs); std_attrs = NULL; - if (c_parser_nth_token_starts_std_attributes (parser, 1)) - { - std_attrs = c_parser_std_attribute_specifier_sequence (parser); - if (c_parser_next_token_is (parser, CPP_SEMICOLON)) - c_parser_error (parser, "expected statement"); - } + if ((have_std_attrs = c_parser_nth_token_starts_std_attributes (parser, + 1))) + std_attrs = c_parser_std_attribute_specifier_sequence (parser); + } + if (std_attrs + && (!c_parser_handle_statement_omp_attributes (parser, std_attrs, &have_std_attrs) + || std_attrs)) + { + if (have_std_attrs && c_parser_next_token_is (parser, CPP_SEMICOLON)) + c_parser_error (parser, "expected statement"); + c_warn_unused_attributes (std_attrs); } - if (std_attrs) - c_warn_unused_attributes (std_attrs); + else if (have_std_attrs && c_parser_next_token_is (parser, CPP_SEMICOLON)) + c_parser_error (parser, "expected statement"); } /* Parse a label (C90 6.6.1, C99 6.8.1, C11 6.8.1). @@ -6935,6 +7553,7 @@ c_parser_statement (c_parser *parser, bool *if_p, location_t *loc_after_labels) c_parser_all_labels (parser); if (loc_after_labels) *loc_after_labels = c_parser_peek_token (parser)->location; + parser->omp_attrs_forbidden_p = false; c_parser_statement_after_labels (parser, if_p, NULL); } @@ -14206,6 +14825,7 @@ c_parser_omp_variable_list (c_parser *parser, bool array_section_p; auto_vec<c_token> tokens; unsigned int tokens_avail = 0; + c_token *saved_tokens = NULL; bool first = true; while (1) @@ -14285,8 +14905,8 @@ c_parser_omp_variable_list (c_parser *parser, tokens.safe_push (eof_token); tokens.safe_push (eof_token); + saved_tokens = parser->tokens; tokens_avail = parser->tokens_avail; - gcc_assert (parser->tokens == &parser->tokens_buf[0]); parser->tokens = tokens.address (); parser->tokens_avail = tokens.length (); } @@ -14503,7 +15123,7 @@ c_parser_omp_variable_list (c_parser *parser, if (kind == OMP_CLAUSE_DEPEND || kind == OMP_CLAUSE_AFFINITY) { - parser->tokens = &parser->tokens_buf[0]; + parser->tokens = saved_tokens; parser->tokens_avail = tokens_avail; } if (c_parser_next_token_is_not (parser, CPP_COMMA)) @@ -18677,6 +19297,7 @@ static tree c_parser_omp_structured_block (c_parser *parser, bool *if_p) { tree stmt = push_stmt_list (); + parser->omp_attrs_forbidden_p = true; c_parser_statement (parser, if_p); return pop_stmt_list (stmt); } @@ -20786,6 +21407,79 @@ c_parser_omp_flush (c_parser *parser) c_finish_omp_flush (loc, mo); } +/* Return true if next tokens contain a standard attribute that contains + omp::directive (DIRECTIVE). */ + +static bool +c_parser_omp_section_scan (c_parser *parser, const char *directive, + bool tentative) +{ + if (!c_parser_nth_token_starts_std_attributes (parser, 1)) + return false; + unsigned int n = 3; + if (!c_parser_check_balanced_raw_token_sequence (parser, &n)) + return false; + c_token *token = c_parser_peek_nth_token_raw (parser, n); + if (token->type != CPP_CLOSE_SQUARE) + return false; + token = c_parser_peek_nth_token_raw (parser, n + 1); + if (token->type != CPP_CLOSE_SQUARE) + return false; + if (n < 9) + return false; + if (c_parser_peek_nth_token_raw (parser, 3)->type == CPP_NAME + && c_parser_peek_nth_token_raw (parser, 4)->type == CPP_OPEN_PAREN + && c_parser_peek_nth_token_raw (parser, 5)->type == CPP_NAME) + { + tree first = c_parser_peek_nth_token_raw (parser, 3)->value; + tree second = c_parser_peek_nth_token_raw (parser, 5)->value; + if (strcmp (IDENTIFIER_POINTER (first), "directive") + && strcmp (IDENTIFIER_POINTER (first), "__directive__")) + return false; + if (strcmp (IDENTIFIER_POINTER (second), directive)) + return false; + } + if (tentative) + return true; + location_t first_loc = c_parser_peek_token (parser)->location; + location_t last_loc = c_parser_peek_nth_token_raw (parser, n + 1)->location; + location_t middle_loc = UNKNOWN_LOCATION; + tree std_attrs = c_parser_std_attribute_specifier_sequence (parser); + bool seen = false; + int cnt = 0; + for (tree attr = std_attrs; attr; attr = TREE_CHAIN (attr)) + if (is_attribute_namespace_p ("omp", attr) + && is_attribute_p ("directive", get_attribute_name (attr))) + { + for (tree a = TREE_VALUE (attr); a; a = TREE_CHAIN (a)) + { + tree d = TREE_VALUE (a); + gcc_assert (TREE_CODE (d) == C_TOKEN_VEC); + c_token *first = C_TOKEN_VEC_TOKENS (d)->address (); + cnt++; + if (first->type == CPP_NAME + && strcmp (IDENTIFIER_POINTER (first->value), + directive) == 0) + { + seen = true; + if (middle_loc == UNKNOWN_LOCATION) + middle_loc = first->location; + } + } + } + if (!seen) + return false; + if (cnt != 1 || TREE_CHAIN (std_attrs)) + { + error_at (make_location (first_loc, last_loc, middle_loc), + "%<[[omp::directive(%s)]]%> must be the only specified " + "attribute on a statement", directive); + return false; + } + c_parser_handle_statement_omp_attributes (parser, std_attrs, NULL); + return true; +} + /* Parse an OpenMP structured block sequence. KIND is the corresponding separating directive. */ @@ -20805,6 +21499,13 @@ c_parser_omp_structured_block_sequence (c_parser *parser, if (kind != PRAGMA_NONE && c_parser_peek_token (parser)->pragma_kind == kind) break; + + if (kind != PRAGMA_NONE + && c_parser_omp_section_scan (parser, + kind == PRAGMA_OMP_SCAN + ? "scan" : "section", false)) + break; + c_parser_statement (parser, NULL); } while (1); @@ -21794,7 +22495,8 @@ c_parser_omp_sections_scope (location_t sections_loc, c_parser *parser) stmt = push_stmt_list (); - if (c_parser_peek_token (parser)->pragma_kind != PRAGMA_OMP_SECTION) + if (c_parser_peek_token (parser)->pragma_kind != PRAGMA_OMP_SECTION + && !c_parser_omp_section_scan (parser, "section", true)) { substmt = c_parser_omp_structured_block_sequence (parser, PRAGMA_OMP_SECTION); @@ -21811,6 +22513,7 @@ c_parser_omp_sections_scope (location_t sections_loc, c_parser *parser) break; loc = c_parser_peek_token (parser)->location; + c_parser_omp_section_scan (parser, "section", false); if (c_parser_peek_token (parser)->pragma_kind == PRAGMA_OMP_SECTION) { c_parser_consume_pragma (parser); @@ -22072,6 +22775,7 @@ c_parser_omp_parallel (location_t loc, c_parser *parser, } block = c_begin_omp_parallel (); + parser->omp_attrs_forbidden_p = true; c_parser_statement (parser, if_p); stmt = c_finish_omp_parallel (loc, clauses, block); @@ -22167,6 +22871,7 @@ c_parser_omp_task (location_t loc, c_parser *parser, bool *if_p) "#pragma omp task"); block = c_begin_omp_task (); + parser->omp_attrs_forbidden_p = true; c_parser_statement (parser, if_p); return c_finish_omp_task (loc, clauses, block); } @@ -23205,6 +23910,13 @@ c_parser_omp_declare_simd (c_parser *parser, enum pragma_context context) IDENTIFIER_POINTER (kind)); break; case pragma_compound: + bool have_std_attrs; + tree std_attrs; + have_std_attrs = c_parser_nth_token_starts_std_attributes (parser, 1); + if (have_std_attrs) + std_attrs = c_parser_std_attribute_specifier_sequence (parser); + else + std_attrs = NULL_TREE; if (c_parser_next_token_is (parser, CPP_KEYWORD) && c_parser_peek_token (parser)->keyword == RID_EXTENSION) { @@ -23213,10 +23925,12 @@ c_parser_omp_declare_simd (c_parser *parser, enum pragma_context context) c_parser_consume_token (parser); while (c_parser_next_token_is (parser, CPP_KEYWORD) && c_parser_peek_token (parser)->keyword == RID_EXTENSION); - if (c_parser_next_tokens_start_declaration (parser)) + if (c_parser_next_tokens_start_declaration (parser) + || c_parser_nth_token_starts_std_attributes (parser, 1)) { c_parser_declaration_or_fndef (parser, true, true, true, true, - true, NULL, &clauses); + true, NULL, &clauses, + have_std_attrs, std_attrs); restore_extension_diagnostics (ext); break; } @@ -23225,7 +23939,8 @@ c_parser_omp_declare_simd (c_parser *parser, enum pragma_context context) else if (c_parser_next_tokens_start_declaration (parser)) { c_parser_declaration_or_fndef (parser, true, true, true, true, true, - NULL, &clauses); + NULL, &clauses, have_std_attrs, + std_attrs); break; } error ("%<#pragma omp declare %s%> must be followed by " @@ -23753,8 +24468,8 @@ c_finish_omp_declare_simd (c_parser *parser, tree fndecl, tree parms, while (parser->tokens_avail > 3) { c_token *token = c_parser_peek_token (parser); - gcc_assert (token->type == CPP_NAME - && strcmp (IDENTIFIER_POINTER (token->value), kind) == 0); + gcc_assert (token->type == CPP_NAME); + kind = IDENTIFIER_POINTER (token->value); c_parser_consume_token (parser); parser->in_pragma = true; @@ -23820,8 +24535,9 @@ c_parser_omp_declare_target (c_parser *parser) } else { + bool attr_syntax = parser->in_omp_attribute_pragma != NULL; c_parser_skip_to_pragma_eol (parser); - c_omp_declare_target_attr attr = { -1 }; + c_omp_declare_target_attr attr = { attr_syntax, -1 }; vec_safe_push (current_omp_declare_target_attribute, attr); return; } @@ -23928,6 +24644,7 @@ c_parser_omp_begin (c_parser *parser) if (strcmp (p, "target") == 0) { c_parser_consume_token (parser); + bool attr_syntax = parser->in_omp_attribute_pragma != NULL; tree clauses = c_parser_omp_all_clauses (parser, OMP_BEGIN_DECLARE_TARGET_CLAUSE_MASK, @@ -23936,7 +24653,7 @@ c_parser_omp_begin (c_parser *parser) for (tree c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE_TYPE) device_type |= OMP_CLAUSE_DEVICE_TYPE_KIND (c); - c_omp_declare_target_attr attr = { device_type }; + c_omp_declare_target_attr attr = { attr_syntax, device_type }; vec_safe_push (current_omp_declare_target_attribute, attr); } else @@ -23948,8 +24665,10 @@ c_parser_omp_begin (c_parser *parser) else if (strcmp (p, "assumes") == 0) { c_parser_consume_token (parser); + bool attr_syntax = parser->in_omp_attribute_pragma != NULL; c_parser_omp_assumption_clauses (parser, false); - current_omp_begin_assumes++; + struct c_omp_begin_assumes_data a = { attr_syntax }; + vec_safe_push (current_omp_begin_assumes, a); } else { @@ -23985,23 +24704,61 @@ c_parser_omp_end (c_parser *parser) c_parser_skip_to_pragma_eol (parser); return; } + bool attr_syntax = parser->in_omp_attribute_pragma != NULL; c_parser_skip_to_pragma_eol (parser); if (!vec_safe_length (current_omp_declare_target_attribute)) error_at (loc, "%<#pragma omp end declare target%> without " "corresponding %<#pragma omp declare target%> or " "%<#pragma omp begin declare target%>"); else - current_omp_declare_target_attribute->pop (); + { + c_omp_declare_target_attr + a = current_omp_declare_target_attribute->pop (); + if (a.attr_syntax != attr_syntax) + { + if (a.attr_syntax) + error_at (loc, + "%qs in attribute syntax terminated " + "with %qs in pragma syntax", + a.device_type >= 0 ? "begin declare target" + : "declare target", + "end declare target"); + else + error_at (loc, + "%qs in pragma syntax terminated " + "with %qs in attribute syntax", + a.device_type >= 0 ? "begin declare target" + : "declare target", + "end declare target"); + } + } } else if (strcmp (p, "assumes") == 0) { c_parser_consume_token (parser); + bool attr_syntax = parser->in_omp_attribute_pragma != NULL; c_parser_skip_to_pragma_eol (parser); - if (!current_omp_begin_assumes) + if (!vec_safe_length (current_omp_begin_assumes)) error_at (loc, "%qs without corresponding %qs", "#pragma omp end assumes", "#pragma omp begin assumes"); else - current_omp_begin_assumes--; + { + c_omp_begin_assumes_data + a = current_omp_begin_assumes->pop (); + if (a.attr_syntax != attr_syntax) + { + if (a.attr_syntax) + error_at (loc, + "%qs in attribute syntax terminated " + "with %qs in pragma syntax", + "begin assumes", "end assumes"); + else + error_at (loc, + "%qs in pragma syntax terminated " + "with %qs in attribute syntax", + "begin assumes", "end assumes"); + } + } } else { @@ -24022,6 +24779,7 @@ static void c_parser_omp_declare_reduction (c_parser *parser, enum pragma_context context) { unsigned int tokens_avail = 0, i; + c_token *saved_tokens = NULL; vec<tree> types = vNULL; vec<c_token> clauses = vNULL; enum tree_code reduc_code = ERROR_MARK; @@ -24192,8 +24950,8 @@ c_parser_omp_declare_reduction (c_parser *parser, enum pragma_context context) int errs = errorcount; FOR_EACH_VEC_ELT (types, i, type) { + saved_tokens = parser->tokens; tokens_avail = parser->tokens_avail; - gcc_assert (parser->tokens == &parser->tokens_buf[0]); if (!clauses.is_empty ()) { parser->tokens = clauses.address (); @@ -24354,7 +25112,7 @@ c_parser_omp_declare_reduction (c_parser *parser, enum pragma_context context) if (!clauses.is_empty ()) { - parser->tokens = &parser->tokens_buf[0]; + parser->tokens = saved_tokens; parser->tokens_avail = tokens_avail; } if (bad) |