On Mon, Oct 27, 2025 at 4:20 PM Anna (navi) Figueiredo Gomes
<[email protected]> wrote:
>
> based on the technical specification 25755[1][2]
>
> the keyword '_Defer' is enabled by default as an extension, while
> 'defer' is gated by '-fdefer-ts'
>
> deferred statements execute at the end of the scope they're added on,
> similar to the cleanup attribute, and they have much of the same
> constraints as statement expressions, with added limitation that no
> local jump shall jump over a defer statement, nor shall return, break,
> and continue, jump out of one
>
> 1: https://open-std.org/JTC1/SC22/WG14/www/docs/n3589.pdf
> 2:
> https://thephd.dev/_vendor/future_cxx/technical%20specification/C%20-%20defer/C%20-%20defer%20Technical%20Specification.pdf
>
> gcc/ChangeLog:
>
> * doc/invoke.texi: Add -fdefer-ts documentation.
> * doc/standards.texi: Likewise.
>
> gcc/c-family/ChangeLog:
>
> * c-common.cc: Add defer and _Defer keywords.
> * c-common.h (enum rid): Add RID_DEFER.
> (D_CXX11): Adjust.
> (D_EXT): Likewise.
> (D_EXT89): Likewise.
> (D_EXT11): Likewise.
> (D_ASM): Likewise.
> (D_OBJC): Likewise.
> (D_CXX_OBJC): Likewise.
> (D_CXXWARN): Likewise.
> (D_CXX_CONCEPTS): Likewise.
> (D_TRANSMEM): Likewise.
> (D_CXX_CHAR8_T): Likewise.
> (D_CXX20): Likewise.
> (D_CXX_COROUTINES): Likewise.
> (D_CXX_MODULES): Likewise.
> (D_DEFER): Add defer keyword mask.
> * c-cppbuiltin.cc (c_cpp_builtins): Set __STDC_DEFER_TS25755__.
> * c.opt: Add -fdefer-ts flag.
>
> gcc/c/ChangeLog:
>
> * c-decl.cc (struct c_spot_bindings): Add defer_blocks.
> (struct c_scope): Add has_defer_block flag.
> (set_spot_bindings): Clear defer_blocks.
> (c_binding_adjust_jump_barrier): Adjust for defer.
> (c_bindings_start_stmt_expr): Rename to c_bindings_start_jump_barrier.
> (c_bindings_start_jump_barrier): Adjust for defer.
> (c_bindings_end_stmt_expr): Rename to c_bindings_end_jump_barrier.
> (c_bindings_end_jump_barrier): Adjust for defer.
> (lookup_label_for_goto): Check defer contraints.
> (check_earlier_gotos): Likewise.
> (c_release_switch_bindings): Likewise.
> (c_check_switch_jump_warnings): Likewise.
> * c-parser.cc (c_parser_defer_statement): New function.
> (c_parser_statement_after_labels): Check defer contraints and parse
> defer.
> * c-tree.h (IN_DEFER_STMT): Add variant.
> (c_bindings_start_stmt_expr): Rename to c_bindings_start_jump_barrier.
> (c_bindings_end_stmt_expr): Rename to c_bindings_end_jump_barrier.
> (c_bindings_start_jump_barrier): New function.
> (c_bindings_end_jump_barrier): New function.
> (c_begin_defer_block): New function.
> (c_end_defer_block): New function.
> * c-typeck.cc (c_finish_return): Adjust.
> (c_finish_bc_stmt): Adjust.
> (c_begin_stmt_expr): Likewise.
> (c_finish_stmt_expr): Likewise.
> (c_begin_defer_block): New function.
> (c_end_defer_block): New function.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.dg/defer-1.c: New test.
> * gcc.dg/defer-2.c: New test.
> * gcc.dg/defer-3.c: New test.
> * gcc.dg/defer-4.c: New test.
>
> Signed-off-by: Anna (navi) Figueiredo Gomes <[email protected]>
> ---
> Notes:
> v5:
> * Add '_Defer' to all c versions as an extension, while keeping
> 'defer' under '-fdefer-ts'.
> v4:
> * remove constraint on local backwards jump, making behaviour the
> same
> as the cleanup attribute.
> v3:
> * remove the need of -std=c2y for -fdefer-ts to take effect
> v2:
> * added a test jumping into a defer block from above.
> * reworded the lookup_label_for_goto error message to mention the
> error
> is a jump into the defer block, instead of out.
> * moved the 'defer' keyword behind a -fdefer-ts flag, and added
> texinfo
> docs plus related failure test.
>
> gcc/c-family/c-common.cc | 2 +
> gcc/c-family/c-common.h | 2 +
> gcc/c-family/c-cppbuiltin.cc | 3 +
> gcc/c-family/c.opt | 4 +
> gcc/c/c-decl.cc | 80 +++++++++--
> gcc/c/c-parser.cc | 41 ++++++
> gcc/c/c-tree.h | 8 +-
> gcc/c/c-typeck.cc | 46 +++++-
> gcc/doc/invoke.texi | 6 +-
> gcc/doc/standards.texi | 3 +-
> gcc/testsuite/gcc.dg/defer-1.c | 255 +++++++++++++++++++++++++++++++++
> gcc/testsuite/gcc.dg/defer-2.c | 76 ++++++++++
> gcc/testsuite/gcc.dg/defer-3.c | 15 ++
> gcc/testsuite/gcc.dg/defer-4.c | 8 ++
> 14 files changed, 528 insertions(+), 21 deletions(-)
> create mode 100644 gcc/testsuite/gcc.dg/defer-1.c
> create mode 100644 gcc/testsuite/gcc.dg/defer-2.c
> create mode 100644 gcc/testsuite/gcc.dg/defer-3.c
> create mode 100644 gcc/testsuite/gcc.dg/defer-4.c
>
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index e7dd4602ac1..4fc31b0c924 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -412,6 +412,7 @@ const struct c_common_resword c_common_reswords[] =
> { "_Decimal64", RID_DFLOAT64, D_CONLY },
> { "_Decimal128", RID_DFLOAT128, D_CONLY },
> { "_Decimal64x", RID_DFLOAT64X, D_CONLY },
> + { "_Defer", RID_DEFER, D_CONLY | D_EXT },
> { "_Fract", RID_FRACT, D_CONLY | D_EXT },
> { "_Accum", RID_ACCUM, D_CONLY | D_EXT },
> { "_Sat", RID_SAT, D_CONLY | D_EXT },
> @@ -518,6 +519,7 @@ const struct c_common_resword c_common_reswords[] =
> { "continue", RID_CONTINUE, 0 },
> { "decltype", RID_DECLTYPE, D_CXXONLY | D_CXX11 | D_CXXWARN },
> { "default", RID_DEFAULT, 0 },
> + { "defer", RID_DEFER, D_DEFER | D_CONLY },
> { "delete", RID_DELETE, D_CXXONLY | D_CXXWARN },
> { "do", RID_DO, 0 },
> { "double", RID_DOUBLE, 0 },
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index 7c7e21d2d0e..92b96c6e561 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -113,6 +113,7 @@ enum rid
> RID_BUILTIN_HAS_ATTRIBUTE, RID_BUILTIN_ASSOC_BARRIER, RID_BUILTIN_STDC,
> RID_BUILTIN_COUNTED_BY_REF,
> RID_DFLOAT32, RID_DFLOAT64, RID_DFLOAT128, RID_DFLOAT64X,
> + RID_DEFER,
>
> /* TS 18661-3 keywords, in the same sequence as the TI_* values. */
> RID_FLOAT16,
> @@ -448,6 +449,7 @@ extern machine_mode c_default_pointer_mode;
> #define D_CXX20 0x8000 /* In C++, C++20 only. */
> #define D_CXX_COROUTINES 0x10000 /* In C++, only with coroutines. */
> #define D_CXX_MODULES 0x20000 /* In C++, only with modules. */
> +#define D_DEFER 0x40000 /* In C, only with -fdefer-ts */
>
> #define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS
> #define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T
> diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc
> index 4aea9028863..638bf810f07 100644
> --- a/gcc/c-family/c-cppbuiltin.cc
> +++ b/gcc/c-family/c-cppbuiltin.cc
> @@ -1596,6 +1596,9 @@ c_cpp_builtins (cpp_reader *pfile)
> if (flag_iso)
> cpp_define (pfile, "__STRICT_ANSI__");
>
> + if (!flag_no_gnu_keywords || flag_defer_ts)
> + builtin_define_with_int_value ("__STDC_DEFER_TS25755__", 1);
So the question becomes should __STDC_DEFER_TS25755__ be defined if
_Defer is only supported or should there be another define for that?
> +
> if (!flag_signed_char)
> cpp_define (pfile, "__CHAR_UNSIGNED__");
>
> diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> index 12877eb0e17..26db46c75f2 100644
> --- a/gcc/c-family/c.opt
> +++ b/gcc/c-family/c.opt
> @@ -1931,6 +1931,10 @@ fdefault-inline
> C++ ObjC++ Ignore
> Does nothing. Preserved for backward compatibility.
>
> +fdefer-ts
> +C Var(flag_defer_ts)
> +Enables support for defer statements from ISO/DIS TS 25755 for ISO C2Y
Maybe:
Enables support for defer keyword and defer statements from ISO/DIS TS
25755 for ISO C2Y
Also I don't see why this can't be enabled for ObjC either.
> +
> fdiagnostics-show-template-tree
> C++ ObjC++ Var(flag_diagnostics_show_template_tree) Init(0)
> Print hierarchical comparisons when template types are mismatched.
> diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
> index 56ca3078226..0bfb964de26 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -397,7 +397,7 @@ struct GTY(()) c_spot_bindings {
> of the label or goto. This lets us look at older or newer
> bindings in the scope, as appropriate. */
> struct c_binding *bindings_in_scope;
> - struct c_jump_barrier stmt_exprs;
> + struct c_jump_barrier stmt_exprs, defer_blocks;
> };
>
> /* This structure is used to keep track of bindings seen when a goto
> @@ -533,6 +533,10 @@ struct GTY((chain_next ("%h.outer"))) c_scope {
> decl_jump_unsafe would return true for any of the bindings. This
> is used to avoid looping over all the bindings unnecessarily. */
> BOOL_BITFIELD has_jump_unsafe_decl : 1;
> +
> + /* True if this scope has any deferred statement. This is used to
> + * check invalid goto jumps. */
> + BOOL_BITFIELD has_defer_block : 1;
> };
>
> /* The scope currently in effect. */
> @@ -986,6 +990,8 @@ set_spot_bindings (struct c_spot_bindings *p, bool
> defining)
> }
> p->stmt_exprs.count = 0;
> p->stmt_exprs.left = false;
> + p->defer_blocks.count = 0;
> + p->defer_blocks.left = false;
> }
>
> /* Update spot bindings P as we pop out of SCOPE. Return true if we
> @@ -1605,9 +1611,12 @@ finish_underspecified_init (tree name, unsigned int
> prev_state)
> /* Adjust the bindings for the start of a statement expression. */
>
> static void
> -c_binding_adjust_jump_barrier (struct c_spot_bindings *binding, bool start)
> +c_binding_adjust_jump_barrier (struct c_spot_bindings *binding,
> + bool defer, bool start)
> {
As mentioned in the other patch, the comment before the function
should describe the (new) arguments.
> - struct c_jump_barrier *barrier = &binding->stmt_exprs;
> + struct c_jump_barrier *barrier = defer
> + ? &binding->defer_blocks
> + : &binding->stmt_exprs;
> barrier->count += start ? 1 : -1;
> if (barrier->count < 0)
> {
> @@ -1617,10 +1626,13 @@ c_binding_adjust_jump_barrier (struct c_spot_bindings
> *binding, bool start)
> }
>
> void
> -c_bindings_start_stmt_expr (struct c_spot_bindings* switch_bindings)
> +c_bindings_start_jump_barrier (bool defer, struct c_spot_bindings*
> switch_bindings)
> {
> struct c_scope *scope;
>
> + if (defer)
> + current_scope->has_defer_block = true;
> +
> for (scope = current_scope; scope != NULL; scope = scope->outer)
> {
> struct c_binding *b;
> @@ -1638,20 +1650,24 @@ c_bindings_start_stmt_expr (struct c_spot_bindings*
> switch_bindings)
> continue;
> label_vars = b->u.label;
>
> - c_binding_adjust_jump_barrier (&label_vars->label_bindings, true);
> + c_binding_adjust_jump_barrier (&label_vars->label_bindings, defer,
> true);
> FOR_EACH_VEC_SAFE_ELT (label_vars->gotos, ix, g)
> - c_binding_adjust_jump_barrier (&g->goto_bindings, true);
> + c_binding_adjust_jump_barrier (&g->goto_bindings, defer, true);
> }
> }
>
> if (switch_bindings != NULL)
> - c_binding_adjust_jump_barrier (switch_bindings, true);
> + {
> + if (defer)
> + switch_bindings->scope->has_defer_block = true;
> + c_binding_adjust_jump_barrier (switch_bindings, defer, true);
> + }
> }
>
> /* Adjust the bindings for the end of a statement expression. */
>
> void
> -c_bindings_end_stmt_expr (struct c_spot_bindings *switch_bindings)
> +c_bindings_end_jump_barrier (bool defer, struct c_spot_bindings
> *switch_bindings)
> {
> struct c_scope *scope;
>
> @@ -1671,9 +1687,9 @@ c_bindings_end_stmt_expr (struct c_spot_bindings
> *switch_bindings)
> if (TREE_CODE (b->decl) != LABEL_DECL)
> continue;
> label_vars = b->u.label;
> - c_binding_adjust_jump_barrier (&label_vars->label_bindings, false);
> + c_binding_adjust_jump_barrier (&label_vars->label_bindings, defer,
> false);
> FOR_EACH_VEC_SAFE_ELT (label_vars->gotos, ix, g)
> - c_binding_adjust_jump_barrier (&g->goto_bindings, false);
> + c_binding_adjust_jump_barrier (&g->goto_bindings, defer, false);
> }
> }
>
> @@ -1681,6 +1697,7 @@ c_bindings_end_stmt_expr (struct c_spot_bindings
> *switch_bindings)
> {
> c_binding_adjust_jump_barrier (switch_bindings, defer, false);
> gcc_assert (switch_bindings->stmt_exprs.count >= 0);
> + gcc_assert (switch_bindings->defer_blocks.count >= 0);
> }
> }
>
> @@ -4158,6 +4175,12 @@ lookup_label_for_goto (location_t loc, tree name)
> inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", label);
> }
>
> + if (label_vars->label_bindings.defer_blocks.left)
> + {
> + error_at (loc, "jump into defer block");
> + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here", label);
> + }
> +
> return label;
> }
>
> @@ -4249,6 +4272,23 @@ check_earlier_gotos (tree label, struct c_label_vars*
> label_vars)
> inform (DECL_SOURCE_LOCATION (label), "label %qD defined here",
> label);
> }
> +
> + if (g->goto_bindings.defer_blocks.count > 0)
> + {
> + error_at (g->loc, "jump into defer block");
> + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here",
> label);
> + }
> + else if (g->goto_bindings.defer_blocks.left)
> + {
> + error_at (g->loc, "jump out of defer block");
> + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here",
> label);
> + }
> + else if (label_vars->label_bindings.scope->has_defer_block
> + || g->goto_bindings.scope->has_defer_block)
> + {
> + error_at (g->loc, "jump over defer block");
> + inform (DECL_SOURCE_LOCATION (label), "label %qD defined here",
> label);
> + }
> }
>
> /* Now that the label is defined, we will issue warnings about
> @@ -4334,6 +4374,7 @@ void
> c_release_switch_bindings (struct c_spot_bindings *bindings)
> {
> gcc_assert (bindings->stmt_exprs.count == 0 && !bindings->stmt_exprs.left);
> + gcc_assert (bindings->defer_blocks.count == 0 &&
> !bindings->defer_blocks.left);
> XDELETE (bindings);
> }
>
> @@ -4405,6 +4446,25 @@ c_check_switch_jump_warnings (struct c_spot_bindings
> *switch_bindings,
> inform (switch_loc, "switch starts here");
> }
>
> + if (switch_bindings->defer_blocks.count > 0)
> + {
> + saw_error = true;
> + error_at (case_loc, "switch jumps into defer block");
> + inform (switch_loc, "switch starts here");
> + }
> + else if (switch_bindings->defer_blocks.left)
> + {
> + saw_error = true;
> + error_at (case_loc, "switch jumps out of defer block");
> + inform (switch_loc, "switch starts here");
> + }
> + else if (switch_bindings->scope->has_defer_block)
> + {
> + saw_error = true;
> + error_at (case_loc, "switch jumps over defer block");
> + inform (switch_loc, "switch starts here");
> + }
> +
> return saw_error;
> }
>
> diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
> index 4a13fc0d384..34b9e334436 100644
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -127,6 +127,8 @@ c_parse_init (void)
> mask |= D_C99;
> if (!flag_isoc23)
> mask |= D_C23;
> + if (!flag_defer_ts)
> + mask |= D_DEFER;
> if (flag_no_asm)
> {
> mask |= D_ASM | D_EXT;
> @@ -1763,6 +1765,7 @@ static void c_parser_statement_after_labels (c_parser
> *, bool *, tree,
> static tree c_parser_c99_block_statement (c_parser *, bool *,
> location_t * = NULL);
> static void c_parser_if_statement (c_parser *, bool *, vec<tree> *);
> +static void c_parser_defer_statement (c_parser *parser, bool *if_p, tree
> before_labels);
> static void c_parser_switch_statement (c_parser *, bool *, tree);
> static void c_parser_while_statement (c_parser *, bool, unsigned short, bool,
> bool *, tree);
> @@ -8228,6 +8231,9 @@ c_parser_statement_after_labels (c_parser *parser, bool
> *if_p,
> case RID_FOR:
> c_parser_for_statement (parser, false, 0, false, if_p,
> before_labels);
> break;
> + case RID_DEFER:
> + c_parser_defer_statement (parser, if_p, before_labels);
> + break;
> case RID_GOTO:
> c_parser_consume_token (parser);
> if (c_parser_next_token_is (parser, CPP_NAME))
> @@ -8754,6 +8760,41 @@ c_parser_if_statement (c_parser *parser, bool *if_p,
> vec<tree> *chain)
> c_parser_maybe_reclassify_token (parser);
> }
>
> +/* Parse a defer statement (ISO/DIS TS 25755 6.4).
> +
> + defer-statement:
> + defer deferred-block */
> +
> +static void
> +c_parser_defer_statement (c_parser *parser, bool *if_p, tree before_labels)
> +{
> + location_t loc = c_parser_peek_token (parser)->location;
> + unsigned char save_in_statement = in_statement;
> + tree deferred;
> +
> + gcc_assert (c_parser_next_token_is_keyword (parser, RID_DEFER));
> + c_parser_consume_token (parser);
> +
> + if (c_parser_next_token_is (parser, CPP_SEMICOLON))
> + {
> + c_end_defer_block (loc, c_begin_defer_block ());
> + add_stmt (build_empty_stmt (loc));
> + c_parser_consume_token (parser);
> + warning_at (loc, OPT_Wempty_body,
> + "suggest braces around empty body in %<defer%> statement");
> + return;
> + }
> +
> + deferred = c_begin_defer_block ();
> +
> + in_statement = IN_DEFER_STMT;
> + c_parser_statement_after_labels (parser, if_p, before_labels);
> + in_statement = save_in_statement;
> +
> + deferred = c_end_defer_block (loc, deferred);
> + push_cleanup(deferred, deferred, false);
> +}
> +
> /* Parse a switch statement (C90 6.6.4, C99 6.8.4, C11 6.8.4).
>
> switch-statement:
> diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
> index bb0b113754e..7ab9d009e76 100644
> --- a/gcc/c/c-tree.h
> +++ b/gcc/c/c-tree.h
> @@ -643,6 +643,7 @@ extern struct obstack parser_obstack;
> #define IN_OMP_FOR 8
> #define IN_OBJC_FOREACH 16
> #define IN_NAMED_STMT 32
> +#define IN_DEFER_STMT 64
> extern unsigned char in_statement;
>
> extern bool switch_statement_break_seen_p;
> @@ -654,8 +655,8 @@ extern void finish_underspecified_init (tree, unsigned
> int);
> extern void push_scope (void);
> extern tree pop_scope (void);
> extern void c_mark_decl_jump_unsafe_in_current_scope ();
> -extern void c_bindings_start_stmt_expr (struct c_spot_bindings *);
> -extern void c_bindings_end_stmt_expr (struct c_spot_bindings *);
> +extern void c_bindings_start_jump_barrier (bool, struct c_spot_bindings *);
> +extern void c_bindings_end_jump_barrier (bool, struct c_spot_bindings *);
>
> extern void record_inline_static (location_t, tree, tree,
> enum c_inline_static_type);
> @@ -869,6 +870,9 @@ extern tree c_end_compound_stmt (location_t, tree, bool);
> extern void c_finish_if_stmt (location_t, tree, tree, tree);
> extern void c_finish_loop (location_t, location_t, tree, location_t, tree,
> tree, tree, tree, bool);
> +
> +extern tree c_begin_defer_block (void);
> +extern tree c_end_defer_block (location_t loc, tree body);
> extern tree c_begin_stmt_expr (void);
> extern tree c_finish_stmt_expr (location_t, tree);
> extern tree c_process_expr_stmt (location_t, tree);
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index f26184e5603..7a939a823e1 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -12871,6 +12871,12 @@ c_finish_return (location_t loc, tree retval, tree
> origtype, bool musttail_p)
> warning_at (xloc, 0,
> "function declared %<noreturn%> has a %<return%> statement");
>
> + if (in_statement & IN_DEFER_STMT)
> + {
> + error_at (loc, "return from defer block");
> + return NULL_TREE;
> + }
> +
> set_musttail_on_return (retval, xloc, musttail_p);
>
> if (retval)
> @@ -13252,6 +13258,9 @@ c_finish_bc_stmt (location_t loc, tree label, bool
> is_break, tree name)
> case IN_OMP_FOR:
> error_at (loc, "break statement used with OpenMP for loop");
> return NULL_TREE;
> + case IN_DEFER_STMT:
> + error_at (loc, "break statement jumps out of defer block");
> + return NULL_TREE;
> case IN_ITERATION_STMT:
> case IN_OBJC_FOREACH:
> break;
> @@ -13269,6 +13278,9 @@ c_finish_bc_stmt (location_t loc, tree label, bool
> is_break, tree name)
> case IN_OMP_BLOCK:
> error_at (loc, "invalid exit from OpenMP structured block");
> return NULL_TREE;
> + case IN_DEFER_STMT:
> + error_at (loc, "continue statement jumps out of defer block");
> + return NULL_TREE;
> case IN_ITERATION_STMT:
> case IN_OMP_FOR:
> case IN_OBJC_FOREACH:
> @@ -13414,9 +13426,9 @@ c_begin_stmt_expr (void)
> keep_next_level ();
> ret = c_begin_compound_stmt (true);
>
> - c_bindings_start_stmt_expr (c_switch_stack == NULL
> - ? NULL
> - : c_switch_stack->bindings);
> + c_bindings_start_jump_barrier (false, c_switch_stack == NULL
> + ? NULL
> + : c_switch_stack->bindings);
>
> /* Mark the current statement list as belonging to a statement list. */
> STATEMENT_LIST_STMT_EXPR (ret) = 1;
> @@ -13435,9 +13447,9 @@ c_finish_stmt_expr (location_t loc, tree body)
>
> body = c_end_compound_stmt (loc, body, true);
>
> - c_bindings_end_stmt_expr (c_switch_stack == NULL
> - ? NULL
> - : c_switch_stack->bindings);
> + c_bindings_end_jump_barrier (false, c_switch_stack == NULL
> + ? NULL
> + : c_switch_stack->bindings);
>
> /* Locate the last statement in BODY. See c_end_compound_stmt
> about always returning a BIND_EXPR. */
> @@ -13532,7 +13544,27 @@ c_finish_stmt_expr (location_t loc, tree body)
> return t;
> }
> }
> -
> +
> +tree
> +c_begin_defer_block (void)
> +{
As mentioned, all functions need a comment before saying what it does
including what the arguments mean. (yes not all GCC follows this, I
have been trying to fix those up).
> + c_bindings_start_jump_barrier (true, c_switch_stack == NULL
> + ? NULL
> + : c_switch_stack->bindings);
> +
> + return c_begin_compound_stmt (true);
> +}
> +
> +tree
> +c_end_defer_block (location_t loc, tree body)
> +{
> + c_bindings_end_jump_barrier (true, c_switch_stack == NULL
> + ? NULL
> + : c_switch_stack->bindings);
> +
> + return c_end_compound_stmt (loc, body, true);
> +}
> +
> /* Begin and end compound statements. This is as simple as pushing
> and popping new statement lists from the tree. */
>
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index c1e708beacf..368aa6ab227 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -206,7 +206,7 @@ in the following sections.
> -fpermitted-flt-eval-methods=@var{standard}
> -fplan9-extensions -fsigned-bitfields -funsigned-bitfields
> -fsigned-char -funsigned-char -fstrict-flex-arrays[=@var{n}]
> --fsso-struct=@var{endianness}}
> +-fsso-struct=@var{endianness} -fdefer-ts}
>
> @item C++ Language Options
> @xref{C++ Dialect Options,,Options Controlling C++ Dialect}.
> @@ -2902,6 +2902,10 @@ the target (the default). This option is not
> supported for C++.
> @strong{Warning:} the @option{-fsso-struct} switch causes GCC to generate
> code that is not binary compatible with code generated without it if the
> specified endianness is not the native endianness of the target.
> +
> +@opindex fdefer-ts
> +@item -fdefer-ts
> +Enables support for the defer keyword, as specified by ISO/DIS TS 25755.
I would add "This option is not supported for C++." like other options
documentation.
Otherwise the patch looks good to me.
Thanks,
Andrew
> @end table
>
> @node C++ Dialect Options
> diff --git a/gcc/doc/standards.texi b/gcc/doc/standards.texi
> index 0d765b17aa2..8d82c92da0e 100644
> --- a/gcc/doc/standards.texi
> +++ b/gcc/doc/standards.texi
> @@ -123,7 +123,8 @@ enabled with @option{-std=c23} or
> @option{-std=iso9899:2024}.
>
> A further version of the C standard, known as @dfn{C2Y}, is under
> development; experimental and incomplete support for this is enabled
> -with @option{-std=c2y}.
> +with @option{-std=c2y}. GCC implements the defer technical specification,
> +ISO/DIS TS 25755, enabled with @option{-fdefer-ts}.
>
> By default, GCC provides some extensions to the C language that, on
> rare occasions conflict with the C standard. @xref{C
> diff --git a/gcc/testsuite/gcc.dg/defer-1.c b/gcc/testsuite/gcc.dg/defer-1.c
> new file mode 100644
> index 00000000000..4b7cbe67921
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/defer-1.c
> @@ -0,0 +1,255 @@
> +/* { dg-do run } */
> +/* { dg-options "-fdefer-ts" } */
> +
> +#include <setjmp.h>
> +
> +#define assert(x) if (!(x)) __builtin_abort ()
> +
> +#if __STDC_DEFER_TS25755__ != 1
> +# error "missing defer flag"
> +#endif
> +
> +int a ()
> +{
> + int r = 5;
> + return r;
> +
> + defer r *= 2;
> +}
> +
> +int b ()
> +{
> + int r = 0;
> + goto b;
> + {
> + defer r += 2;
> + }
> +b:
> + r += 5;
> + return r;
> +}
> +
> +int c ()
> +{
> + int r = 0;
> + {
> + defer r += 2;
> + goto b;
> + }
> +b:
> + r += 5;
> + return r;
> +}
> +
> +int d ()
> +{
> + int r = 0;
> + goto b;
> + {
> +b:
> + defer r += 2;
> + }
> + r += 5;
> + return r;
> +}
> +
> +int e ()
> +{
> + int r = 0;
> + {
> + goto b;
> + defer r += 2;
> + }
> +b:;
> + r += 5;
> + return r;
> +}
> +
> +int f ()
> +{
> + int r = 4;
> + int *p = &r;
> + defer *p = 5;
> + return *p;
> +}
> +
> +int g ()
> +{
> + int r = 1;
> + {
> + defer r += 2;
> + if (true)
> + defer r *= 2;
> + r += 10;
> + }
> + return r;
> +}
> +
> +int h ()
> +{
> + int r = 1;
> + {
> + defer r += 2;
> + for (int i = 0; i < 5; i++)
> + defer r *= 2;
> + }
> + return r;
> +}
> +
> +int i ()
> +{
> + int r = 0;
> + {
> + defer {
> + defer r *= 4;
> + r *= 2;
> + defer {
> + r += 3;
> + }
> + }
> + defer r += 1;
> + }
> +
> + return r;
> +}
> +
> +int j ()
> +{
> + int r = 0;
> + {
> + defer if (0) r = 5;
> + }
> + return r;
> +}
> +
> +int global = 0;
> +
> +int k ()
> +{
> + global = 5;
> + defer global = 10;
> + return global;
> +}
> +
> +int l ()
> +{
> + int r = 5;
> + int j = 2;
> +
> + {
> + defer {
> + int j = 10;
> + r *= j;
> + }
> + }
> +
> + return r;
> +}
> +
> +int m ()
> +{
> + int r = 0;
> +
> + void clear(int *value) {
> + *value = 10;
> + }
> +
> + {
> + [[gnu::cleanup(clear)]] int j = 1;
> + defer r = j * 2;
> + }
> + return r;
> +}
> +
> +int n ()
> +{
> + jmp_buf env;
> + int r = 0;
> +
> + if (setjmp(env) != 0)
> + return r;
> +
> + defer r = 1;
> +
> + {
> + defer r = 2;
> + r++;
> + longjmp(env, 1);
> + }
> +}
> +
> +int o ()
> +{
> + int r = 0;
> +
> + int foo() {
> + r = 2;
> + defer r *= 2;
> + return r;
> + }
> +
> + return foo() + r;
> +}
> +
> +int p (int *arg)
> +{
> + *arg = 2;
> + defer *arg *= 2;
> + return *arg;
> +}
> +
> +int q ()
> +{
> + int r = 1;
> + {
> +b:
> + defer r *= 2;
> + if (r < 10)
> + goto b;
> + }
> +
> + return r;
> +}
> +
> +int r ()
> +{
> + int r = 1;
> + {
> + defer r *= 2;
> +b:
> + if (r < 10)
> + {
> + r++;
> + goto b;
> + }
> + }
> +
> + return r;
> +}
> +
> +int
> +main ()
> +{
> + int arg;
> +
> + assert (a () == 5);
> + assert (b () == 5);
> + assert (c () == 7);
> + assert (d () == 7);
> + assert (e () == 5);
> + assert (f () == 4);
> + assert (g () == 14);
> + assert (h () == 34);
> + assert (i () == 20);
> + assert (j () == 0);
> + assert (k () == 5 && global == 10);
> + assert (l () == 50);
> + assert (m () == 2);
> + assert (n () == 1);
> + assert (o () == 6);
> + assert (p (&arg) == 2 && arg == 4);
> + assert (q () == 32);
> + assert (r () == 20);
> +
> + return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/defer-2.c b/gcc/testsuite/gcc.dg/defer-2.c
> new file mode 100644
> index 00000000000..5123024f11d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/defer-2.c
> @@ -0,0 +1,76 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fdefer-ts" } */
> +
> +void a ()
> +{
> + goto b; /* { dg-error "jump over defer block" } */
> + defer;
> +b:
> +}
> +
> +void b () {
> + defer {
> + goto b; /* { dg-error "jump out of defer block" } */
> + }
> +b:
> +}
> +
> +int c () {
> + defer {
> + return 5; /* { dg-error "return from defer block" } */
> + }
> + return 1;
> +}
> +
> +void d () {
> + defer {
> +b:
> + }
> + goto b; /* { dg-error "jump into defer block" } */
> +}
> +
> +void e () {
> + goto b; /* { dg-error "jump over defer block" } */
> + {
> + defer;
> +b:
> + }
> +}
> +
> +void f ()
> +{
> + goto b; /* { dg-error "jump into defer block" } */
> + defer {
> +b:
> + };
> +}
> +
> +void g () {
> + switch (1) {
> + defer;
> + default: /* { dg-error "switch jumps over defer block" } */
> + defer;
> + break;
> + }
> +}
> +
> +void h () {
> + switch (1) {
> + default:
> + defer {
> + break; /* { dg-error "break statement jumps out of defer block" } */
> + }
> + }
> +
> + for (;;) {
> + defer {
> + break; /* { dg-error "break statement jumps out of defer block" } */
> + }
> + }
> +
> + for (;;) {
> + defer {
> + continue; /* { dg-error "continue statement jumps out of defer block"
> } */
> + }
> + }
> +}
> diff --git a/gcc/testsuite/gcc.dg/defer-3.c b/gcc/testsuite/gcc.dg/defer-3.c
> new file mode 100644
> index 00000000000..55217c15557
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/defer-3.c
> @@ -0,0 +1,15 @@
> +/* { dg-do run } */
> +/* { dg-options "-fdefer-ts" } */
> +
> +extern void exit(int);
> +extern void abort(void);
> +
> +int main(void) {
> + {
> + defer {
> + exit(0);
> + }
> + }
> + abort();
> + return 1;
> +}
> diff --git a/gcc/testsuite/gcc.dg/defer-4.c b/gcc/testsuite/gcc.dg/defer-4.c
> new file mode 100644
> index 00000000000..67dd5d29365
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/defer-4.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "" } */
> +
> +int main(void)
> +{
> + _Defer;
> + defer; /* { dg-error "'defer' undeclared" "undeclared identifier" } */
> +}
> --
> 2.51.0
>