diff options
-rw-r--r-- | gcc/c-family/c-attribs.cc | 15 | ||||
-rw-r--r-- | gcc/c-family/c-lex.cc | 2 | ||||
-rw-r--r-- | gcc/c/c-parser.cc | 113 | ||||
-rw-r--r-- | gcc/cp/constexpr.cc | 90 | ||||
-rw-r--r-- | gcc/cp/cp-gimplify.cc | 44 | ||||
-rw-r--r-- | gcc/cp/cp-tree.h | 4 | ||||
-rw-r--r-- | gcc/cp/parser.cc | 101 | ||||
-rw-r--r-- | gcc/cp/pt.cc | 27 | ||||
-rw-r--r-- | gcc/cp/semantics.cc | 75 | ||||
-rw-r--r-- | gcc/doc/extend.texi | 49 | ||||
-rw-r--r-- | gcc/fold-const.cc | 28 | ||||
-rw-r--r-- | gcc/fold-const.h | 1 | ||||
-rw-r--r-- | gcc/gimplify.cc | 19 | ||||
-rw-r--r-- | gcc/internal-fn.cc | 6 | ||||
-rw-r--r-- | gcc/internal-fn.def | 4 | ||||
-rw-r--r-- | gcc/internal-fn.h | 1 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/attr-assume1.C | 191 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/attr-assume2.C | 83 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/attr-assume3.C | 198 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/attr-assume4.C | 136 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C | 88 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C | 76 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/attr-assume-1.c | 69 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/attr-assume-2.c | 66 | ||||
-rw-r--r-- | gcc/testsuite/gcc.dg/attr-assume-3.c | 35 |
25 files changed, 1319 insertions, 202 deletions
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index 8bb80e2..671ea38 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -144,6 +144,7 @@ static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *); static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *); static tree handle_alloc_align_attribute (tree *, tree, tree, int, bool *); static tree handle_assume_aligned_attribute (tree *, tree, tree, int, bool *); +static tree handle_assume_attribute (tree *, tree, tree, int, bool *); static tree handle_target_attribute (tree *, tree, tree, int, bool *); static tree handle_target_clones_attribute (tree *, tree, tree, int, bool *); static tree handle_optimize_attribute (tree *, tree, tree, int, bool *); @@ -530,6 +531,8 @@ const struct attribute_spec c_common_attribute_table[] = handle_designated_init_attribute, NULL }, { "fallthrough", 0, 0, false, false, false, false, handle_fallthrough_attribute, NULL }, + { "assume", 1, 1, false, false, false, false, + handle_assume_attribute, NULL }, { "patchable_function_entry", 1, 2, true, false, false, false, handle_patchable_function_entry_attribute, NULL }, @@ -5741,6 +5744,18 @@ handle_fallthrough_attribute (tree *, tree name, tree, int, return NULL_TREE; } +/* Handle a "assume" attribute; arguments as in struct + attribute_spec.handler. */ + +tree +handle_assume_attribute (tree *, tree name, tree, int, + bool *no_add_attrs) +{ + pedwarn (input_location, OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + return NULL_TREE; +} + /* Handle a "patchable_function_entry" attributes; arguments as in struct attribute_spec.handler. */ diff --git a/gcc/c-family/c-lex.cc b/gcc/c-family/c-lex.cc index d4e448a..050fa77 100644 --- a/gcc/c-family/c-lex.cc +++ b/gcc/c-family/c-lex.cc @@ -378,6 +378,8 @@ c_common_has_attribute (cpp_reader *pfile, bool std_syntax) result = 201803; else if (is_attribute_p ("nodiscard", attr_name)) result = 201907; + else if (is_attribute_p ("assume", attr_name)) + result = 202207; } else { diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc index f6a94ba..9b76637 100644 --- a/gcc/c/c-parser.cc +++ b/gcc/c/c-parser.cc @@ -1823,6 +1823,46 @@ add_debug_begin_stmt (location_t loc) add_stmt (stmt); } +/* Helper function for c_parser_declaration_or_fndef and + Handle assume attribute(s). */ + +static tree +handle_assume_attribute (location_t here, tree attrs, bool nested) +{ + if (nested) + for (tree attr = lookup_attribute ("gnu", "assume", attrs); attr; + attr = lookup_attribute ("gnu", "assume", TREE_CHAIN (attr))) + { + tree args = TREE_VALUE (attr); + int nargs = list_length (args); + if (nargs != 1) + { + error_at (here, "wrong number of arguments specified " + "for %qE attribute", + get_attribute_name (attr)); + inform (here, "expected %i, found %i", 1, nargs); + } + else + { + tree arg = TREE_VALUE (args); + arg = c_objc_common_truthvalue_conversion (here, arg); + arg = c_fully_fold (arg, false, NULL); + if (arg != error_mark_node) + { + tree fn = build_call_expr_internal_loc (here, IFN_ASSUME, + void_type_node, 1, + arg); + add_stmt (fn); + } + } + } + else + pedwarn (here, OPT_Wattributes, + "%<assume%> attribute at top level"); + + return remove_attribute ("gnu", "assume", attrs); +} + /* Parse a declaration or function definition (C90 6.5, 6.7.1, C99 6.7, 6.9.1, C11 6.7, 6.9.1). If FNDEF_OK is true, a function definition is accepted; otherwise (old-style parameter declarations) only other @@ -2037,6 +2077,14 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, bool auto_type_p = specs->typespec_word == cts_auto_type; if (c_parser_next_token_is (parser, CPP_SEMICOLON)) { + bool handled_assume = false; + if (specs->typespec_kind == ctsk_none + && lookup_attribute ("gnu", "assume", specs->attrs)) + { + handled_assume = true; + specs->attrs + = handle_assume_attribute (here, specs->attrs, nested); + } if (auto_type_p) error_at (here, "%<__auto_type%> in empty declaration"); else if (specs->typespec_kind == ctsk_none @@ -2054,13 +2102,15 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, pedwarn (here, OPT_Wattributes, "%<fallthrough%> attribute at top level"); } - else if (empty_ok && !(have_attrs - && specs->non_std_attrs_seen_p)) + else if (empty_ok + && !(have_attrs && specs->non_std_attrs_seen_p) + && !handled_assume) shadow_tag (specs); else { shadow_tag_warned (specs, 1); - pedwarn (here, 0, "empty declaration"); + if (!handled_assume) + pedwarn (here, 0, "empty declaration"); } c_parser_consume_token (parser); if (oacc_routine_data) @@ -2160,6 +2210,9 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, else if (attribute_fallthrough_p (specs->attrs)) warning_at (here, OPT_Wattributes, "%<fallthrough%> attribute not followed by %<;%>"); + else if (lookup_attribute ("gnu", "assume", specs->attrs)) + warning_at (here, OPT_Wattributes, + "%<assume%> attribute not followed by %<;%>"); pending_xref_error (); prefix_attrs = specs->attrs; @@ -4598,7 +4651,8 @@ c_parser_gnu_attribute_any_word (c_parser *parser) static tree c_parser_attribute_arguments (c_parser *parser, bool takes_identifier, - bool require_string, bool allow_empty_args) + bool require_string, bool assume_attr, + bool allow_empty_args) { vec<tree, va_gc> *expr_list; tree attr_args; @@ -4617,6 +4671,7 @@ c_parser_attribute_arguments (c_parser *parser, bool takes_identifier, == CPP_CLOSE_PAREN)) && (takes_identifier || (c_dialect_objc () + && !assume_attr && c_parser_peek_token (parser)->id_kind == C_ID_CLASSNAME))) { @@ -4653,6 +4708,23 @@ c_parser_attribute_arguments (c_parser *parser, bool takes_identifier, tree string = c_parser_string_literal (parser, false, true).value; attr_args = build_tree_list (NULL_TREE, string); } + else if (assume_attr) + { + tree cond + = c_parser_conditional_expression (parser, NULL, NULL_TREE).value; + if (!c_parser_next_token_is (parser, CPP_COMMA)) + attr_args = build_tree_list (NULL_TREE, cond); + else + { + tree tree_list; + c_parser_consume_token (parser); + expr_list = c_parser_expr_list (parser, false, true, + NULL, NULL, NULL, NULL); + tree_list = build_tree_list_vec (expr_list); + attr_args = tree_cons (NULL_TREE, cond, tree_list); + release_tree_vector (expr_list); + } + } else { expr_list = c_parser_expr_list (parser, false, true, @@ -4736,7 +4808,9 @@ c_parser_gnu_attribute (c_parser *parser, tree attrs, tree attr_args = c_parser_attribute_arguments (parser, attribute_takes_identifier_p (attr_name), - false, true); + false, + is_attribute_p ("assume", attr_name), + true); attr = build_tree_list (attr_name, attr_args); if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN)) @@ -4982,9 +5056,13 @@ c_parser_std_attribute (c_parser *parser, bool for_tm) = (ns == NULL_TREE && (strcmp (IDENTIFIER_POINTER (name), "deprecated") == 0 || strcmp (IDENTIFIER_POINTER (name), "nodiscard") == 0)); + bool assume_attr + = (ns != NULL_TREE + && strcmp (IDENTIFIER_POINTER (ns), "gnu") == 0 + && strcmp (IDENTIFIER_POINTER (name), "assume") == 0); TREE_VALUE (attribute) = c_parser_attribute_arguments (parser, takes_identifier, - require_string, false); + require_string, assume_attr, false); } else c_parser_balanced_token_sequence (parser); @@ -6264,8 +6342,21 @@ c_parser_statement_after_labels (c_parser *parser, bool *if_p, break; case RID_ATTRIBUTE: { - /* Allow '__attribute__((fallthrough));'. */ + /* Allow '__attribute__((fallthrough));' or + '__attribute__((assume(cond)));'. */ tree attrs = c_parser_gnu_attributes (parser); + bool has_assume = lookup_attribute ("assume", attrs); + if (has_assume) + { + if (c_parser_next_token_is (parser, CPP_SEMICOLON)) + attrs = handle_assume_attribute (loc, attrs, true); + else + { + warning_at (loc, OPT_Wattributes, + "%<assume%> attribute not followed by %<;%>"); + has_assume = false; + } + } if (attribute_fallthrough_p (attrs)) { if (c_parser_next_token_is (parser, CPP_SEMICOLON)) @@ -6282,9 +6373,13 @@ c_parser_statement_after_labels (c_parser *parser, bool *if_p, "%<fallthrough%> attribute not followed " "by %<;%>"); } + else if (has_assume) + /* Eat the ';'. */ + c_parser_consume_token (parser); else if (attrs != NULL_TREE) - warning_at (loc, OPT_Wattributes, "only attribute %<fallthrough%>" - " can be applied to a null statement"); + warning_at (loc, OPT_Wattributes, + "only attribute %<fallthrough%> or %<assume%> can " + "be applied to a null statement"); break; } default: diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index db7571d..06dcd71 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -38,6 +38,7 @@ along with GCC; see the file COPYING3. If not see #include "opts.h" #include "stringpool.h" #include "attribs.h" +#include "fold-const.h" static bool verify_constant (tree, bool, bool *, bool *); #define VERIFY_CONSTANT(X) \ @@ -1818,6 +1819,52 @@ cx_error_context (void) return r; } +/* If we have a condition in conjunctive normal form (CNF), find the first + failing clause. In other words, given an expression like + + true && true && false && true && false + + return the first 'false'. EXPR is the expression. */ + +static tree +find_failing_clause_r (constexpr_ctx *ctx, tree expr) +{ + if (TREE_CODE (expr) == TRUTH_ANDIF_EXPR) + { + /* First check the left side... */ + tree e = find_failing_clause_r (ctx, TREE_OPERAND (expr, 0)); + if (e == NULL_TREE) + /* ...if we didn't find a false clause, check the right side. */ + e = find_failing_clause_r (ctx, TREE_OPERAND (expr, 1)); + return e; + } + tree e = contextual_conv_bool (expr, tf_none); + if (ctx) + { + bool new_non_constant_p = false, new_overflow_p = false; + e = cxx_eval_constant_expression (ctx, e, vc_prvalue, + &new_non_constant_p, + &new_overflow_p); + } + else + e = fold_non_dependent_expr (e, tf_none, /*manifestly_const_eval=*/true); + if (integer_zerop (e)) + /* This is the failing clause. */ + return expr; + return NULL_TREE; +} + +/* Wrapper for find_failing_clause_r. */ + +tree +find_failing_clause (constexpr_ctx *ctx, tree expr) +{ + if (TREE_CODE (expr) == TRUTH_ANDIF_EXPR) + if (tree e = find_failing_clause_r (ctx, expr)) + expr = e; + return expr; +} + /* Evaluate a call T to a GCC internal function when possible and return the evaluated result or, under the control of CTX, give an error, set NON_CONSTANT_P, and return the unevaluated call T otherwise. */ @@ -1837,6 +1884,48 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t, case IFN_FALLTHROUGH: return void_node; + case IFN_ASSUME: + /* For now, restrict constexpr evaluation of [[assume (cond)]] + only to the cases which don't have side-effects. Evaluating + it even when it does would mean we'd need to somehow undo + all the side-effects e.g. in ctx->global->values. */ + if (!TREE_SIDE_EFFECTS (CALL_EXPR_ARG (t, 0)) + /* And it needs to be a potential constant expression. */ + && potential_rvalue_constant_expression (CALL_EXPR_ARG (t, 0))) + { + constexpr_ctx new_ctx = *ctx; + new_ctx.quiet = true; + tree arg = CALL_EXPR_ARG (t, 0); + bool new_non_constant_p = false, new_overflow_p = false; + arg = cxx_eval_constant_expression (&new_ctx, arg, vc_prvalue, + &new_non_constant_p, + &new_overflow_p); + if (!new_non_constant_p && !new_overflow_p && integer_zerop (arg)) + { + if (!*non_constant_p && !ctx->quiet) + { + /* See if we can find which clause was failing + (for logical AND). */ + tree bad = find_failing_clause (&new_ctx, + CALL_EXPR_ARG (t, 0)); + /* If not, or its location is unusable, fall back to the + previous location. */ + location_t cloc = cp_expr_loc_or_loc (bad, EXPR_LOCATION (t)); + + auto_diagnostic_group d; + + /* Report the error. */ + error_at (cloc, + "failed %<assume%> attribute assumption"); + diagnose_failing_condition (bad, cloc, false); + } + + *non_constant_p = true; + return t; + } + } + return void_node; + case IFN_ADD_OVERFLOW: opcode = PLUS_EXPR; break; @@ -8706,6 +8795,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, case IFN_UBSAN_BOUNDS: case IFN_UBSAN_VPTR: case IFN_FALLTHROUGH: + case IFN_ASSUME: return true; case IFN_ADD_OVERFLOW: diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index 404a769..b4599fc3 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -3081,6 +3081,50 @@ process_stmt_hotness_attribute (tree std_attrs, location_t attrs_loc) return std_attrs; } +/* If [[assume (cond)]] appears on this statement, handle it. */ + +tree +process_stmt_assume_attribute (tree std_attrs, tree statement, + location_t attrs_loc) +{ + if (std_attrs == error_mark_node) + return std_attrs; + tree attr = lookup_attribute ("gnu", "assume", std_attrs); + if (!attr) + return std_attrs; + /* The next token after the assume attribute is not ';'. */ + if (statement) + { + warning_at (attrs_loc, OPT_Wattributes, + "%<assume%> attribute not followed by %<;%>"); + attr = NULL_TREE; + } + for (; attr; attr = lookup_attribute ("gnu", "assume", TREE_CHAIN (attr))) + { + tree args = TREE_VALUE (attr); + int nargs = list_length (args); + if (nargs != 1) + { + auto_diagnostic_group d; + error_at (attrs_loc, "wrong number of arguments specified for " + "%qE attribute", get_attribute_name (attr)); + inform (attrs_loc, "expected %i, found %i", 1, nargs); + } + else + { + tree arg = TREE_VALUE (args); + if (!type_dependent_expression_p (arg)) + arg = contextual_conv_bool (arg, tf_warning_or_error); + if (error_operand_p (arg)) + continue; + statement = build_call_expr_internal_loc (attrs_loc, IFN_ASSUME, + void_type_node, 1, arg); + finish_expr_stmt (statement); + } + } + return remove_attribute ("gnu", "assume", std_attrs); +} + /* Helper of fold_builtin_source_location, return the std::source_location::__impl type after performing verification on it. LOC is used for reporting any errors. */ diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 8cf9707..8bc1c2d 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7715,6 +7715,7 @@ extern tree build_transaction_expr (location_t, tree, int, tree); extern bool cxx_omp_create_clause_info (tree, tree, bool, bool, bool, bool); extern tree baselink_for_fns (tree); +extern void diagnose_failing_condition (tree, location_t, bool); extern void finish_static_assert (tree, tree, location_t, bool, bool); extern tree finish_decltype_type (tree, bool, tsubst_flags_t); @@ -8242,6 +8243,7 @@ extern tree predeclare_vla (tree); extern void clear_fold_cache (void); extern tree lookup_hotness_attribute (tree); extern tree process_stmt_hotness_attribute (tree, location_t); +extern tree process_stmt_assume_attribute (tree, tree, location_t); extern bool simple_empty_class_p (tree, tree, tree_code); extern tree fold_builtin_source_location (location_t); @@ -8447,6 +8449,8 @@ extern tree fold_sizeof_expr (tree); extern void clear_cv_and_fold_caches (void); extern tree unshare_constructor (tree CXX_MEM_STAT_INFO); extern bool decl_implicit_constexpr_p (tree); +struct constexpr_ctx; +extern tree find_failing_clause (constexpr_ctx *ctx, tree); extern bool replace_decl (tree *, tree, tree); /* An RAII sentinel used to restrict constexpr evaluation so that it diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 7b41635..baa808a 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -2258,7 +2258,7 @@ static vec<tree, va_gc> *cp_parser_parenthesized_expression_list (cp_parser *, int, bool, bool, bool *, location_t * = NULL, bool = false); /* Values for the second parameter of cp_parser_parenthesized_expression_list. */ -enum { non_attr = 0, normal_attr = 1, id_attr = 2 }; +enum { non_attr = 0, normal_attr = 1, id_attr = 2, assume_attr = 3 }; static void cp_parser_pseudo_destructor_name (cp_parser *, tree, tree *, tree *); static cp_expr cp_parser_unary_expression @@ -2287,6 +2287,7 @@ static cp_expr cp_parser_binary_expression (cp_parser *, bool, bool, enum cp_parser_prec, cp_id_kind *); static tree cp_parser_question_colon_clause (cp_parser *, cp_expr); +static cp_expr cp_parser_conditional_expression (cp_parser *); static cp_expr cp_parser_assignment_expression (cp_parser *, cp_id_kind * = NULL, bool = false, bool = false); static enum tree_code cp_parser_assignment_operator_opt @@ -8480,7 +8481,6 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, bool wrap_locations_p) { vec<tree, va_gc> *expression_list; - tree identifier = NULL_TREE; bool saved_greater_than_is_operator_p; /* Assume all the expressions will be constant. */ @@ -8509,33 +8509,26 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, next token is an identifier. */ if (is_attribute_list == id_attr && cp_lexer_peek_token (parser->lexer)->type == CPP_NAME) - { - cp_token *token; - - /* Consume the identifier. */ - token = cp_lexer_consume_token (parser->lexer); - /* Save the identifier. */ - identifier = token->u.value; - } + expr = cp_lexer_consume_token (parser->lexer)->u.value; + else if (is_attribute_list == assume_attr) + expr = cp_parser_conditional_expression (parser); else - { - expr - = cp_parser_parenthesized_expression_list_elt (parser, cast_p, - allow_expansion_p, - non_constant_p); + expr + = cp_parser_parenthesized_expression_list_elt (parser, cast_p, + allow_expansion_p, + non_constant_p); - if (wrap_locations_p) - expr.maybe_add_location_wrapper (); + if (wrap_locations_p) + expr.maybe_add_location_wrapper (); - /* Add it to the list. We add error_mark_node - expressions to the list, so that we can still tell if - the correct form for a parenthesized expression-list - is found. That gives better errors. */ - vec_safe_push (expression_list, expr.get_value ()); + /* Add it to the list. We add error_mark_node + expressions to the list, so that we can still tell if + the correct form for a parenthesized expression-list + is found. That gives better errors. */ + vec_safe_push (expression_list, expr.get_value ()); - if (expr == error_mark_node) - goto skip_comma; - } + if (expr == error_mark_node) + goto skip_comma; /* After the first item, attribute lists look the same as expression lists. */ @@ -8577,9 +8570,6 @@ cp_parser_parenthesized_expression_list (cp_parser* parser, parser->greater_than_is_operator_p = saved_greater_than_is_operator_p; - if (identifier) - vec_safe_insert (expression_list, 0, identifier); - return expression_list; } @@ -10310,7 +10300,8 @@ cp_parser_binary_expression (cp_parser* parser, bool cast_p, logical-or-expression that started the conditional-expression. Returns a representation of the entire conditional-expression. - This routine is used by cp_parser_assignment_expression. + This routine is used by cp_parser_assignment_expression + and cp_parser_conditional_expression. ? expression : assignment-expression @@ -10377,6 +10368,28 @@ cp_parser_question_colon_clause (cp_parser* parser, cp_expr logical_or_expr) tf_warning_or_error); } +/* Parse a conditional-expression. + + conditional-expression: + logical-or-expression + logical-or-expression ? expression : assignment-expression + + GNU Extensions: + + logical-or-expression ? : assignment-expression */ + +static cp_expr +cp_parser_conditional_expression (cp_parser *parser) +{ + cp_expr expr = cp_parser_binary_expression (parser, false, false, false, + PREC_NOT_OPERATOR, NULL); + /* If the next token is a `?' then we're actually looking at + a conditional-expression; otherwise we're done. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_QUERY)) + return cp_parser_question_colon_clause (parser, expr); + return expr; +} + /* Parse an assignment-expression. assignment-expression: @@ -10702,15 +10715,7 @@ cp_parser_constant_expression (cp_parser* parser, determine whether a particular assignment-expression is in fact constant. */ if (strict_p) - { - /* Parse the binary expressions (logical-or-expression). */ - expression = cp_parser_binary_expression (parser, false, false, false, - PREC_NOT_OPERATOR, NULL); - /* If the next token is a `?' then we're actually looking at - a conditional-expression; otherwise we're done. */ - if (cp_lexer_next_token_is (parser->lexer, CPP_QUERY)) - expression = cp_parser_question_colon_clause (parser, expression); - } + expression = cp_parser_conditional_expression (parser); else expression = cp_parser_assignment_expression (parser); /* Restore the old settings. */ @@ -12503,6 +12508,9 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, /* Look for an expression-statement instead. */ statement = cp_parser_expression_statement (parser, in_statement_expr); + std_attrs = process_stmt_assume_attribute (std_attrs, statement, + attrs_loc); + /* Handle [[fallthrough]];. */ if (attribute_fallthrough_p (std_attrs)) { @@ -12526,7 +12534,7 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, if (statement && STATEMENT_CODE_P (TREE_CODE (statement))) SET_EXPR_LOCATION (statement, statement_location); - /* Allow "[[fallthrough]];", but warn otherwise. */ + /* Allow "[[fallthrough]];" or "[[assume(cond)]];", but warn otherwise. */ if (std_attrs != NULL_TREE) warning_at (attrs_loc, OPT_Wattributes, @@ -12718,6 +12726,8 @@ cp_parser_expression_statement (cp_parser* parser, tree in_statement_expr) } } + attr = process_stmt_assume_attribute (attr, statement, loc); + /* Handle [[fallthrough]];. */ if (attribute_fallthrough_p (attr)) { @@ -28876,6 +28886,8 @@ cp_parser_gnu_attribute_list (cp_parser* parser, bool exactly_one /* = false */) vec<tree, va_gc> *vec; int attr_flag = (attribute_takes_identifier_p (identifier) ? id_attr : normal_attr); + if (is_attribute_p ("assume", identifier)) + attr_flag = assume_attr; vec = cp_parser_parenthesized_expression_list (parser, attr_flag, /*cast_p=*/false, /*allow_expansion_p=*/false, @@ -29127,6 +29139,9 @@ cp_parser_std_attribute (cp_parser *parser, tree attr_ns) /* C++17 fallthrough attribute is equivalent to GNU's. */ else if (is_attribute_p ("fallthrough", attr_id)) TREE_PURPOSE (TREE_PURPOSE (attribute)) = gnu_identifier; + /* C++23 assume attribute is equivalent to GNU's. */ + else if (is_attribute_p ("assume", attr_id)) + TREE_PURPOSE (TREE_PURPOSE (attribute)) = gnu_identifier; /* Transactional Memory TS optimize_for_synchronized attribute is equivalent to GNU transaction_callable. */ else if (is_attribute_p ("optimize_for_synchronized", attr_id)) @@ -29171,8 +29186,12 @@ cp_parser_std_attribute (cp_parser *parser, tree attr_ns) return error_mark_node; } - if (attr_ns == gnu_identifier - && attribute_takes_identifier_p (attr_id)) + if (is_attribute_p ("assume", attr_id) + && (attr_ns == NULL_TREE || attr_ns == gnu_identifier)) + /* The assume attribute needs special handling of the argument. */ + attr_flag = assume_attr; + else if (attr_ns == gnu_identifier + && attribute_takes_identifier_p (attr_id)) /* A GNU attribute that takes an identifier in parameter. */ attr_flag = id_attr; diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index bce2a77..bf4ae02 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -21163,6 +21163,33 @@ tsubst_copy_and_build (tree t, break; } + case IFN_ASSUME: + gcc_assert (nargs == 1); + if (vec_safe_length (call_args) != 1) + { + error_at (cp_expr_loc_or_input_loc (t), + "wrong number of arguments to " + "%<assume%> attribute"); + ret = error_mark_node; + } + else + { + tree &arg = (*call_args)[0]; + if (!type_dependent_expression_p (arg)) + arg = contextual_conv_bool (arg, tf_warning_or_error); + if (error_operand_p (arg)) + { + ret = error_mark_node; + break; + } + ret = build_call_expr_internal_loc (EXPR_LOCATION (t), + IFN_ASSUME, + void_type_node, 1, + arg); + RETURN (ret); + } + break; + default: /* Unsupported internal function with arguments. */ gcc_unreachable (); diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 30cf2f9..39b11ee 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -11172,42 +11172,31 @@ init_cp_semantics (void) } -/* If we have a condition in conjunctive normal form (CNF), find the first - failing clause. In other words, given an expression like +/* Emit additional diagnostics for failing condition BAD. + Used by finish_static_assert and IFN_ASSUME constexpr diagnostics. + If SHOW_EXPR_P is true, print the condition (because it was + instantiation-dependent). */ - true && true && false && true && false - - return the first 'false'. EXPR is the expression. */ - -static tree -find_failing_clause_r (tree expr) +void +diagnose_failing_condition (tree bad, location_t cloc, bool show_expr_p) { - if (TREE_CODE (expr) == TRUTH_ANDIF_EXPR) + /* Nobody wants to see the artificial (bool) cast. */ + bad = tree_strip_nop_conversions (bad); + + /* Actually explain the failure if this is a concept check or a + requires-expression. */ + if (concept_check_p (bad) || TREE_CODE (bad) == REQUIRES_EXPR) + diagnose_constraints (cloc, bad, NULL_TREE); + else if (COMPARISON_CLASS_P (bad) + && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0)))) { - /* First check the left side... */ - tree e = find_failing_clause_r (TREE_OPERAND (expr, 0)); - if (e == NULL_TREE) - /* ...if we didn't find a false clause, check the right side. */ - e = find_failing_clause_r (TREE_OPERAND (expr, 1)); - return e; + tree op0 = fold_non_dependent_expr (TREE_OPERAND (bad, 0)); + tree op1 = fold_non_dependent_expr (TREE_OPERAND (bad, 1)); + tree cond = build2 (TREE_CODE (bad), boolean_type_node, op0, op1); + inform (cloc, "the comparison reduces to %qE", cond); } - tree e = contextual_conv_bool (expr, tf_none); - e = fold_non_dependent_expr (e, tf_none, /*manifestly_const_eval=*/true); - if (integer_zerop (e)) - /* This is the failing clause. */ - return expr; - return NULL_TREE; -} - -/* Wrapper for find_failing_clause_r. */ - -static tree -find_failing_clause (tree expr) -{ - if (TREE_CODE (expr) == TRUTH_ANDIF_EXPR) - if (tree e = find_failing_clause_r (expr)) - expr = e; - return expr; + else if (show_expr_p) + inform (cloc, "%qE evaluates to false", bad); } /* Build a STATIC_ASSERT for a static assertion with the condition @@ -11274,12 +11263,12 @@ finish_static_assert (tree condition, tree message, location_t location, int len = TREE_STRING_LENGTH (message) / sz - 1; /* See if we can find which clause was failing (for logical AND). */ - tree bad = find_failing_clause (orig_condition); + tree bad = find_failing_clause (NULL, orig_condition); /* If not, or its location is unusable, fall back to the previous location. */ location_t cloc = cp_expr_loc_or_loc (bad, location); - /* Nobody wants to see the artificial (bool) cast. */ - bad = tree_strip_nop_conversions (bad); + + auto_diagnostic_group d; /* Report the error. */ if (len == 0) @@ -11288,21 +11277,7 @@ finish_static_assert (tree condition, tree message, location_t location, error_at (cloc, "static assertion failed: %s", TREE_STRING_POINTER (message)); - /* Actually explain the failure if this is a concept check or a - requires-expression. */ - if (concept_check_p (bad) - || TREE_CODE (bad) == REQUIRES_EXPR) - diagnose_constraints (location, bad, NULL_TREE); - else if (COMPARISON_CLASS_P (bad) - && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0)))) - { - tree op0 = fold_non_dependent_expr (TREE_OPERAND (bad, 0)); - tree op1 = fold_non_dependent_expr (TREE_OPERAND (bad, 1)); - tree cond = build2 (TREE_CODE (bad), boolean_type_node, op0, op1); - inform (cloc, "the comparison reduces to %qE", cond); - } - else if (show_expr_p) - inform (cloc, "%qE evaluates to false", bad); + diagnose_failing_condition (bad, cloc, show_expr_p); } else if (condition && condition != error_mark_node) { diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index a5afb46..9ddfcf7 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -9187,6 +9187,20 @@ available for functions (@pxref{Function Attributes}), variables (@pxref{Variable Attributes}), labels (@pxref{Label Attributes}), enumerators (@pxref{Enumerator Attributes}), and for types (@pxref{Type Attributes}). +@table @code +@item fallthrough +@cindex @code{fallthrough} statement attribute +The @code{fallthrough} attribute with a null statement serves as a +fallthrough statement. It hints to the compiler that a statement +that falls through to another case label, or user-defined label +in a switch statement is intentional and thus the +@option{-Wimplicit-fallthrough} warning must not trigger. The +fallthrough attribute may appear at most once in each attribute +list, and may not be mixed with other attributes. It can only +be used in a switch statement (the compiler will issue an error +otherwise), after a preceding statement and before a logically +succeeding case label, or user-defined label. + This example uses the @code{fallthrough} statement attribute to indicate that the @option{-Wimplicit-fallthrough} warning should not be emitted: @@ -9201,19 +9215,28 @@ switch (cond) @} @end smallexample -@table @code -@item fallthrough -@cindex @code{fallthrough} statement attribute -The @code{fallthrough} attribute with a null statement serves as a -fallthrough statement. It hints to the compiler that a statement -that falls through to another case label, or user-defined label -in a switch statement is intentional and thus the -@option{-Wimplicit-fallthrough} warning must not trigger. The -fallthrough attribute may appear at most once in each attribute -list, and may not be mixed with other attributes. It can only -be used in a switch statement (the compiler will issue an error -otherwise), after a preceding statement and before a logically -succeeding case label, or user-defined label. +@item assume +@cindex @code{assume} statement attribute +The @code{assume} attribute with a null statement serves as portable +assumption. It should have a single argument, a conditional expression, +which is not evaluated. If the argument would evaluate to true +at the point where it appears, it has no effect, otherwise there +is undefined behavior. This is a GNU variant of the ISO C++23 +standard @code{assume} attribute, but it can be used in any version of +both C and C++. + +@smallexample +int +foo (int x, int y) +@{ + __attribute__((assume(x == 42))); + __attribute__((assume(++y == 43))); + return x + y; +@} +@end smallexample + +@code{y} is not actually incremented and the compiler can but does not +have to optimize it to just @code{return 42 + 42;}. @end table diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc index 4f4ec81..9f7beae 100644 --- a/gcc/fold-const.cc +++ b/gcc/fold-const.cc @@ -130,7 +130,6 @@ static tree eval_subst (location_t, tree, tree, tree, tree, tree); static tree optimize_bit_field_compare (location_t, enum tree_code, tree, tree, tree); static bool simple_operand_p (const_tree); -static bool simple_operand_p_2 (tree); static tree range_binop (enum tree_code, tree, tree, int, tree, int); static tree range_predecessor (tree); static tree range_successor (tree); @@ -4868,8 +4867,8 @@ sign_bit_p (tree exp, const_tree val) return NULL_TREE; } -/* Subroutine for fold_truth_andor_1: determine if an operand is simple enough - to be evaluated unconditionally. */ +/* Subroutine for fold_truth_andor_1 and simple_condition_p: determine if an + operand is simple enough to be evaluated unconditionally. */ static bool simple_operand_p (const_tree exp) @@ -4897,13 +4896,12 @@ simple_operand_p (const_tree exp) && (! TREE_STATIC (exp) || DECL_REGISTER (exp)))); } -/* Subroutine for fold_truth_andor: determine if an operand is simple enough - to be evaluated unconditionally. - I addition to simple_operand_p, we assume that comparisons, conversions, +/* Determine if an operand is simple enough to be evaluated unconditionally. + In addition to simple_operand_p, we assume that comparisons, conversions, and logic-not operations are simple, if their operands are simple, too. */ -static bool -simple_operand_p_2 (tree exp) +bool +simple_condition_p (tree exp) { enum tree_code code; @@ -4920,7 +4918,7 @@ simple_operand_p_2 (tree exp) && simple_operand_p (TREE_OPERAND (exp, 1))); if (code == TRUTH_NOT_EXPR) - return simple_operand_p_2 (TREE_OPERAND (exp, 0)); + return simple_condition_p (TREE_OPERAND (exp, 0)); return simple_operand_p (exp); } @@ -9787,10 +9785,10 @@ fold_truth_andor (location_t loc, enum tree_code code, tree type, side-effects, or isn't simple, then we can't add to it, as otherwise we might destroy if-sequence. */ if (TREE_CODE (arg0) == icode - && simple_operand_p_2 (arg1) + && simple_condition_p (arg1) /* Needed for sequence points to handle trappings, and side-effects. */ - && simple_operand_p_2 (TREE_OPERAND (arg0, 1))) + && simple_condition_p (TREE_OPERAND (arg0, 1))) { tem = fold_build2_loc (loc, ncode, type, TREE_OPERAND (arg0, 1), arg1); @@ -9800,10 +9798,10 @@ fold_truth_andor (location_t loc, enum tree_code code, tree type, /* Same as above but for (A AND[-IF] (B AND-IF C)) -> ((A AND B) AND-IF C), or (A OR[-IF] (B OR-IF C) -> ((A OR B) OR-IF C). */ else if (TREE_CODE (arg1) == icode - && simple_operand_p_2 (arg0) + && simple_condition_p (arg0) /* Needed for sequence points to handle trappings, and side-effects. */ - && simple_operand_p_2 (TREE_OPERAND (arg1, 0))) + && simple_condition_p (TREE_OPERAND (arg1, 0))) { tem = fold_build2_loc (loc, ncode, type, arg0, TREE_OPERAND (arg1, 0)); @@ -9814,8 +9812,8 @@ fold_truth_andor (location_t loc, enum tree_code code, tree type, into (A OR B). For sequence point consistancy, we need to check for trapping, and side-effects. */ - else if (code == icode && simple_operand_p_2 (arg0) - && simple_operand_p_2 (arg1)) + else if (code == icode && simple_condition_p (arg0) + && simple_condition_p (arg1)) return fold_build2_loc (loc, ncode, type, arg0, arg1); } diff --git a/gcc/fold-const.h b/gcc/fold-const.h index fe78a4d..fa284c7 100644 --- a/gcc/fold-const.h +++ b/gcc/fold-const.h @@ -215,6 +215,7 @@ extern tree build_range_check (location_t, tree, tree, int, tree, tree); extern bool merge_ranges (int *, tree *, tree *, int, tree, tree, int, tree, tree); extern tree sign_bit_p (tree, const_tree); +extern bool simple_condition_p (tree); extern tree exact_inverse (tree, tree); extern bool expr_not_equal_to (tree t, const wide_int &); extern tree const_unop (enum tree_code, tree, tree); diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc index 9e0e342..95e16f6 100644 --- a/gcc/gimplify.cc +++ b/gcc/gimplify.cc @@ -3554,6 +3554,25 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value) enum internal_fn ifn = CALL_EXPR_IFN (*expr_p); auto_vec<tree> vargs (nargs); + if (ifn == IFN_ASSUME) + { + if (simple_condition_p (CALL_EXPR_ARG (*expr_p, 0))) + { + /* If the [[assume (cond)]]; condition is simple + enough and can be evaluated unconditionally + without side-effects, expand it as + if (!cond) __builtin_unreachable (); */ + tree fndecl = builtin_decl_explicit (BUILT_IN_UNREACHABLE); + *expr_p = build3 (COND_EXPR, void_type_node, + CALL_EXPR_ARG (*expr_p, 0), void_node, + build_call_expr_loc (EXPR_LOCATION (*expr_p), + fndecl, 0)); + return GS_OK; + } + /* FIXME: Otherwise expand it specially. */ + return GS_ALL_DONE; + } + for (i = 0; i < nargs; i++) { gimplify_arg (&CALL_EXPR_ARG (*expr_p, i), pre_p, diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc index c306240..de608bd4 100644 --- a/gcc/internal-fn.cc +++ b/gcc/internal-fn.cc @@ -4522,3 +4522,9 @@ expand_TRAP (internal_fn, gcall *) { expand_builtin_trap (); } + +void +expand_ASSUME (internal_fn, gcall *) +{ + gcc_unreachable (); +} diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def index f49b395..61516da 100644 --- a/gcc/internal-fn.def +++ b/gcc/internal-fn.def @@ -462,6 +462,10 @@ DEF_INTERNAL_FN (TRAP, ECF_CONST | ECF_LEAF | ECF_NORETURN | ECF_NOTHROW | ECF_COLD | ECF_LOOPING_CONST_OR_PURE, NULL) +/* [[assume (cond)]]. */ +DEF_INTERNAL_FN (ASSUME, ECF_CONST | ECF_LEAF | ECF_NOTHROW + | ECF_LOOPING_CONST_OR_PURE, NULL) + #undef DEF_INTERNAL_INT_FN #undef DEF_INTERNAL_FLT_FN #undef DEF_INTERNAL_FLT_FLOATN_FN diff --git a/gcc/internal-fn.h b/gcc/internal-fn.h index c4c94f8..21b1ce4 100644 --- a/gcc/internal-fn.h +++ b/gcc/internal-fn.h @@ -243,6 +243,7 @@ extern void expand_PHI (internal_fn, gcall *); extern void expand_SHUFFLEVECTOR (internal_fn, gcall *); extern void expand_SPACESHIP (internal_fn, gcall *); extern void expand_TRAP (internal_fn, gcall *); +extern void expand_ASSUME (internal_fn, gcall *); extern bool vectorized_internal_fn_supported_p (internal_fn, tree); diff --git a/gcc/testsuite/g++.dg/cpp23/attr-assume1.C b/gcc/testsuite/g++.dg/cpp23/attr-assume1.C new file mode 100644 index 0000000..76b61e9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/attr-assume1.C @@ -0,0 +1,191 @@ +// P1774R8 - Portable assumptions +// { dg-do run { target c++11 } } + +namespace std +{ + constexpr bool + isfinite (float x) + { return __builtin_isfinite (x); } + + constexpr bool + isfinite (double x) + { return __builtin_isfinite (x); } + + constexpr bool + isfinite (long double x) + { return __builtin_isfinite (x); } + + constexpr float + sqrt (float x) + { return __builtin_sqrtf (x); } + + constexpr double + sqrt (double x) + { return __builtin_sqrt (x); } + + constexpr long double + sqrt (long double x) + { return __builtin_sqrtl (x); } + + extern "C" void + abort (); +} + +constexpr int +f1 (int i) +{ +#if __cpp_constexpr >= 201603L + auto f = [=] { [[assume (i == 0)]]; }; + return sizeof (f); +#else + return sizeof (int); +#endif +} + +void +f2 () +{ + static_assert (f1 (0) >= sizeof (int), ""); +} + +int +f3 (int i) +{ + [[assume (i == 42)]]; + return i; +} + +int +f4 (int i) +{ + [[assume (++i == 44)]]; + return i; +} + +int a; +int *volatile c; + +bool +f5 () +{ + ++a; + return true; +} + +constexpr int +f6 () +{ +#if __cpp_constexpr >= 201304L + [[assume (f5 ())]]; +#endif + return 1; +} + +template <int ...args> +bool +f7 () +{ +#if __cpp_fold_expressions >= 201411L + [[assume (((args >= 0) && ...))]]; + return ((args >= 0) && ...); +#else + return true; +#endif +} + +bool +f8 (double x) +{ + [[assume (std::isfinite (x) && x >= 0.0)]]; + return std::isfinite (std::sqrt (x)); +} + +double +f9 (double x) +{ + [[assume (std::isfinite (std::sqrt (x)))]]; + return std::sqrt (x); +} + +template <typename T, T N> +T +f10 (T x) +{ + [[assume (x == N)]]; + return x; +} + +int +f11 (int x) +{ + [[assume (x == 93 ? true : throw 1)]]; + return x; +} + +constexpr int +f12 (int x) +{ +#if __cpp_constexpr >= 201304L + [[assume (++x == 43)]]; +#endif + return x; +} + +static_assert (f12 (42) == 42, ""); + +struct S +{ + operator bool () { return true; } +}; + +int +f13 () +{ + S s; + [[assume (s)]]; + return 0; +} + +template <typename T> +int +f14 () +{ + T t; + [[assume (t)]]; + return 0; +} + +int +main () +{ + int b = 42; + double d = 42.0, e = 43.0; + c = &b; + [[assume (f5 ())]]; + if (a) + std::abort (); + [[assume (++b == 43)]]; + if (b != 42 || *c != 42) + std::abort (); + static_assert (f6 () == 1, ""); + if (f6 () != 1) + std::abort (); + if (a) + std::abort (); + if (!f7 <0> () || !f7 <1, 2, 3, 4> ()) + std::abort (); + [[assume (d < e)]]; + if (f10 <int, 45> (45) != 45 + || f10 <long long, 128LL> (128LL) != 128LL +#if __cpp_nontype_template_args >= 201911L + || f10 <long double, -42.0L> (-42.0L) != -42.0L +#endif + || false) + std::abort (); + int i = 90, j = 91, k = 92; + [[assume (i == 90), assume (j <= 91)]] [[assume (k >= 92)]]; + if (f11 (93) != 93) + std::abort (); + if (f14 <S> () != 0) + std::abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp23/attr-assume2.C b/gcc/testsuite/g++.dg/cpp23/attr-assume2.C new file mode 100644 index 0000000..9e54c14 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/attr-assume2.C @@ -0,0 +1,83 @@ +// P1774R8 - Portable assumptions +// { dg-do compile { target c++11 } } + +[[assume (true)]] void f1 (); // { dg-error "'assume' attribute ignored" } +typedef int intx [[assume (true)]]; // { dg-error "'assume' attribute ignored" } +[[assume (true)]]; // { dg-warning "attribute ignored" } + +void +foo () +{ + int i; + [[assume]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } + [[assume ()]]; // { dg-error "parentheses must be omitted if 'assume' attribute argument list is empty" } + // { dg-error "wrong number of arguments specified for 'assume' attribute" "" { target *-*-* } .-1 } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-2 } + [[assume (true, true)]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } + [[assume (true)]] i = 1; // { dg-warning "'assume' attribute not followed by ';'" } + [[assume (throw 1)]]; // { dg-error "expected primary-expression before 'throw'" } + [[assume (i = 1)]]; // { dg-error "expected '\\\)' before '=' token" } +} + +constexpr int +f2 (int x) +{ +#if __cpp_constexpr >= 201304L + [[assume (x == 42)]]; // { dg-error "failed 'assume' attribute assumption" "" { target c++14 } } +#endif // { dg-message "the comparison reduces to '\\\(x == 42\\\)'" "" { target c++14 } .-1 } + return x; +} + +constexpr int a = f2 (44); + +int +f3 (int x) +{ + __asm ("" : "+r" (x)); + return x; +} + +constexpr int +f4 (int x) +{ +#if __cpp_constexpr >= 201304L + [[assume (f3 (42) == 42)]]; +#endif + return x; +} + +static_assert (f4 (42) == 42, ""); + +struct S {}; + +int +f5 () +{ + S s; + [[assume (s)]]; // { dg-error "could not convert 's' from 'S' to 'bool'" } + return 0; +} + +template <typename T> +int +f6 () +{ + T t; + [[assume (t)]]; // { dg-error "could not convert 't' from 'S' to 'bool'" } + return 0; +} + +int z = f6 <S> (); + +constexpr int +f7 (int x, int y, int z, int w) +{ +#if __cpp_constexpr >= 201304L + [[assume (x == 42 && y == 43 && z == 44 && w == 45)]]; // { dg-error "failed 'assume' attribute assumption" "" { target c++14 } } +#endif // { dg-message "the comparison reduces to '\\\(z == 44\\\)'" "" { target c++14 } .-1 } + return x; +} + +constexpr int w = f7 (42, 43, 45, 44); diff --git a/gcc/testsuite/g++.dg/cpp23/attr-assume3.C b/gcc/testsuite/g++.dg/cpp23/attr-assume3.C new file mode 100644 index 0000000..0be28c7 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/attr-assume3.C @@ -0,0 +1,198 @@ +// P1774R8 - Portable assumptions +// { dg-do run { target c++11 } } + +namespace std +{ + constexpr bool + isfinite (float x) + { return __builtin_isfinite (x); } + + constexpr bool + isfinite (double x) + { return __builtin_isfinite (x); } + + constexpr bool + isfinite (long double x) + { return __builtin_isfinite (x); } + + constexpr float + sqrt (float x) + { return __builtin_sqrtf (x); } + + constexpr double + sqrt (double x) + { return __builtin_sqrt (x); } + + constexpr long double + sqrt (long double x) + { return __builtin_sqrtl (x); } + + extern "C" void + abort (); +} + +constexpr int +f1 (int i) +{ +#if __cpp_constexpr >= 201603L + auto f = [=] { [[__assume__ (i == 0)]]; }; + return sizeof (f); +#else + return sizeof (int); +#endif +} + +void +f2 () +{ + static_assert (f1 (0) >= sizeof (int), ""); +} + +int +f3 (int i) +{ + [[gnu::assume (i == 42)]]; + return i; +} + +int +f4 (int i) +{ + __attribute__ ((assume (++i == 44))); + return i; +} + +int a; +int *volatile c; + +bool +f5 () +{ + ++a; + return true; +} + +constexpr int +f6 () +{ +#if __cpp_constexpr >= 201304L + [[__assume__ (f5 ())]]; +#endif + return 1; +} + +template <int ...args> +bool +f7 () +{ +#if __cpp_fold_expressions >= 201411L + [[__gnu__::__assume__ (((args >= 0) && ...))]]; + return ((args >= 0) && ...); +#else + return true; +#endif +} + +bool +f8 (double x) +{ + [[gnu::assume (std::isfinite (x) && x >= 0.0)]]; + return std::isfinite (std::sqrt (x)); +} + +double +f9 (double x) +{ + __attribute__((assume (std::isfinite (std::sqrt (x))))); + return std::sqrt (x); +} + +template <typename T, T N> +T +f10 (T x) +{ + [[__assume__ (x == N)]]; + return x; +} + +int +f11 (int x) +{ + [[gnu::assume (x == 93 ? true : throw 1)]]; + return x; +} + +constexpr int +f12 (int x) +{ +#if __cpp_constexpr >= 201304L + __attribute__((assume (++x == 43))); +#endif + return x; +} + +static_assert (f12 (42) == 42, ""); + +struct S +{ + operator bool () { return true; } +}; + +int +f13 () +{ + S s; + [[__gnu__::__assume__ (s)]]; + return 0; +} + +template <typename T> +int +f14 () +{ + T t; + __attribute__((assume (t))); + return 0; +} + +int +main () +{ + int b = 42; + double d = 42.0, e = 43.0; + c = &b; + [[__assume__ (f5 ())]]; + if (a) + std::abort (); + [[gnu::assume (++b == 43)]]; + if (b != 42 || *c != 42) + std::abort (); + static_assert (f6 () == 1, ""); + if (f6 () != 1) + std::abort (); + if (a) + std::abort (); + if (!f7 <0> () || !f7 <1, 2, 3, 4> ()) + std::abort (); + __attribute__((assume (d < e))); + if (f10 <int, 45> (45) != 45 + || f10 <long long, 128LL> (128LL) != 128LL +#if __cpp_nontype_template_args >= 201911L + || f10 <long double, -42.0L> (-42.0L) != -42.0L +#endif + || false) + std::abort (); + int i = 90, j = 91, k = 92; + [[__assume__ (i == 90), gnu::assume (j <= 91)]] +#if __cplusplus >= 201703L + [[using gnu:assume (k >= 92)]] +#else + [[gnu::assume (k >= 92)]] +#endif + ; + __attribute__((__assume__ (i == 90), assume (j <= 91))) __attribute__((assume (k >= 92))); + if (f11 (93) != 93) + std::abort (); + if (f14 <S> () != 0) + std::abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp23/attr-assume4.C b/gcc/testsuite/g++.dg/cpp23/attr-assume4.C new file mode 100644 index 0000000..059d47b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/attr-assume4.C @@ -0,0 +1,136 @@ +// P1774R8 - Portable assumptions +// { dg-do compile { target c++11 } } + +[[__assume__ (true)]] void f1 (); // { dg-error "'assume' attribute ignored" } +typedef int intx [[__assume__ (true)]]; // { dg-error "'assume' attribute ignored" } +[[__assume__ (true)]]; // { dg-warning "attribute ignored" } +[[gnu::assume (true)]] void f1a (); // { dg-error "'assume' attribute ignored" } +typedef int inty [[gnu::__assume__ (true)]]; // { dg-error "'assume' attribute ignored" } +[[__gnu__::assume (true)]]; // { dg-warning "attribute ignored" } +__attribute__((assume (true))) void f1b (); // { dg-error "'assume' attribute ignored" } +typedef int intz __attribute__((assume (true)));// { dg-error "'assume' attribute ignored" } + +void +foo () +{ + int i; + [[__assume__]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } + [[__assume__ ()]]; // { dg-error "parentheses must be omitted if 'assume' attribute argument list is empty" } + // { dg-error "wrong number of arguments specified for 'assume' attribute" "" { target *-*-* } .-1 } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-2 } + [[__assume__ (true, true)]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } + [[__assume__ (true)]] i = 1; // { dg-warning "'assume' attribute not followed by ';'" } + [[__assume__ (throw 1)]]; // { dg-error "expected primary-expression before 'throw'" } + [[__assume__ (i = 1)]]; // { dg-error "expected '\\\)' before '=' token" } + [[gnu::assume]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } + [[gnu::assume ()]]; // { dg-error "parentheses must be omitted if 'assume' attribute argument list is empty" } + // { dg-error "wrong number of arguments specified for 'assume' attribute" "" { target *-*-* } .-1 } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-2 } + [[gnu::assume (true, true)]]; // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } + [[gnu::assume (true)]] i = 1; // { dg-warning "'assume' attribute not followed by ';'" } + [[gnu::assume (throw 1)]]; // { dg-error "expected primary-expression before 'throw'" } + [[gnu::assume (i = 1)]]; // { dg-error "expected '\\\)' before '=' token" } + __attribute__((assume)); // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } + __attribute__((assume ())); // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } + __attribute__((assume (true, true))); // { dg-error "wrong number of arguments specified for 'assume' attribute" } + // { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } + __attribute__((assume (true))) i = 1; // { dg-warning "'assume' attribute not followed by ';'" } + __attribute__((assume (throw 1))); // { dg-error "expected primary-expression before 'throw'" } + __attribute__((assume (i = 1))); // { dg-error "expected '\\\)' before '=' token" } +} + +constexpr int +f2 (int x) +{ +#if __cpp_constexpr >= 201304L + [[__assume__ (x == 42)]]; // { dg-error "failed 'assume' attribute assumption" "" { target c++14 } } +#endif + return x; +} + +constexpr int +f2a (int x) +{ +#if __cpp_constexpr >= 201304L + [[gnu::__assume__ (x == 42)]]; // { dg-error "failed 'assume' attribute assumption" "" { target c++14 } } +#endif + return x; +} + +constexpr int +f2b (int x) +{ +#if __cpp_constexpr >= 201304L + __attribute__((__assume__ (x == 42)));// { dg-error "failed 'assume' attribute assumption" "" { target c++14 } } +#endif + return x; +} + +constexpr int a = f2 (44); +constexpr int aa = f2a (44); +constexpr int ab = f2b (44); + +int +f3 (int x) +{ + __asm ("" : "+r" (x)); + return x; +} + +constexpr int +f4 (int x) +{ +#if __cpp_constexpr >= 201304L + [[__assume__ (f3 (42) == 42)]]; +#endif + return x; +} + +constexpr int +f4a (int x) +{ +#if __cpp_constexpr >= 201304L + [[gnu::assume (f3 (42) == 42)]]; +#endif + return x; +} + +constexpr int +f4b (int x) +{ +#if __cpp_constexpr >= 201304L + __attribute__((assume (f3 (42) == 42))); +#endif + return x; +} + +static_assert (f4 (42) == 42, ""); +static_assert (f4a (42) == 42, ""); +static_assert (f4b (42) == 42, ""); + +struct S {}; + +int +f5 () +{ + S s; + [[gnu::assume (s)]]; // { dg-error "could not convert 's' from 'S' to 'bool'" } + return 0; +} + +template <typename T> +int +f6 () +{ + T t; + __attribute__((assume (t))); // { dg-error "could not convert 't' from 'S' to 'bool'" } + return 0; +} + +int z = f6 <S> (); diff --git a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C index b52cf37..efe9770 100644 --- a/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C +++ b/gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C @@ -422,7 +422,7 @@ # error "__cpp_nontype_template_parameter_auto != 201606" #endif -// C++20 features +// C++20 features: #ifndef __cpp_conditional_explicit # error "__cpp_conditional_explicit" @@ -460,6 +460,44 @@ # error "__cpp_aggregate_paren_init != 201902" #endif +#ifndef __cpp_char8_t +# error "__cpp_char8_t" +#elif __cpp_char8_t != 202207 +# error "__cpp_char8_t != 202207" +#endif + +#ifndef __cpp_designated_initializers +# error "__cpp_designated_initializers" +#elif __cpp_designated_initializers != 201707 +# error "__cpp_designated_initializers != 201707" +#endif + +#ifndef __cpp_constexpr_in_decltype +# error "__cpp_constexpr_in_decltype" +#elif __cpp_constexpr_in_decltype != 201711 +# error "__cpp_constexpr_in_decltype != 201711" +#endif + +#ifndef __cpp_consteval +# error "__cpp_consteval" +#elif __cpp_consteval != 201811 +# error "__cpp_consteval != 201811" +#endif + +#ifndef __cpp_concepts +# error "__cpp_concepts" +#elif __cpp_concepts != 202002 +# error "__cpp_concepts != 202002" +#endif + +#ifndef __cpp_using_enum +# error "__cpp_using_enum" +#elif __cpp_using_enum != 201907 +# error "__cpp_using_enum != 201907" +#endif + +// C++20 attributes: + #ifdef __has_cpp_attribute # if ! __has_cpp_attribute(maybe_unused) @@ -502,42 +540,6 @@ # error "__has_cpp_attribute" #endif -#ifndef __cpp_char8_t -# error "__cpp_char8_t" -#elif __cpp_char8_t != 202207 -# error "__cpp_char8_t != 202207" -#endif - -#ifndef __cpp_designated_initializers -# error "__cpp_designated_initializers" -#elif __cpp_designated_initializers != 201707 -# error "__cpp_designated_initializers != 201707" -#endif - -#ifndef __cpp_constexpr_in_decltype -# error "__cpp_constexpr_in_decltype" -#elif __cpp_constexpr_in_decltype != 201711 -# error "__cpp_constexpr_in_decltype != 201711" -#endif - -#ifndef __cpp_consteval -# error "__cpp_consteval" -#elif __cpp_consteval != 201811 -# error "__cpp_consteval != 201811" -#endif - -#ifndef __cpp_concepts -# error "__cpp_concepts" -#elif __cpp_concepts != 202002 -# error "__cpp_concepts != 202002" -#endif - -#ifndef __cpp_using_enum -# error "__cpp_using_enum" -#elif __cpp_using_enum != 201907 -# error "__cpp_using_enum != 201907" -#endif - // C++23 features: #ifndef __cpp_size_t_suffix @@ -575,3 +577,15 @@ #elif __cpp_implicit_move != 202207 # error "__cpp_implicit_move != 202207" #endif + +// C++23 attributes: + +#ifdef __has_cpp_attribute +# if ! __has_cpp_attribute(assume) +# error "__has_cpp_attribute(assume)" +# elif __has_cpp_attribute(assume) != 202207 +# error "__has_cpp_attribute(assume) != 202207" +# endif +#else +# error "__has_cpp_attribute" +#endif diff --git a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C index 02f3a37..16bc0b8 100644 --- a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C +++ b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C @@ -422,7 +422,7 @@ # error "__cpp_nontype_template_parameter_auto != 201606" #endif -// C++20 features +// C++20 features: #ifndef __cpp_conditional_explicit # error "__cpp_conditional_explicit" @@ -460,6 +460,44 @@ # error "__cpp_aggregate_paren_init != 201902" #endif +#ifndef __cpp_char8_t +# error "__cpp_char8_t" +#elif __cpp_char8_t != 202207 +# error "__cpp_char8_t != 202207" +#endif + +#ifndef __cpp_designated_initializers +# error "__cpp_designated_initializers" +#elif __cpp_designated_initializers != 201707 +# error "__cpp_designated_initializers != 201707" +#endif + +#ifndef __cpp_constexpr_in_decltype +# error "__cpp_constexpr_in_decltype" +#elif __cpp_constexpr_in_decltype != 201711 +# error "__cpp_constexpr_in_decltype != 201711" +#endif + +#ifndef __cpp_consteval +# error "__cpp_consteval" +#elif __cpp_consteval != 201811 +# error "__cpp_consteval != 201811" +#endif + +#ifndef __cpp_concepts +# error "__cpp_concepts" +#elif __cpp_concepts != 202002 +# error "__cpp_concepts != 202002" +#endif + +#ifndef __cpp_using_enum +# error "__cpp_using_enum" +#elif __cpp_using_enum != 201907 +# error "__cpp_using_enum != 201907" +#endif + +// C++20 attributes: + #ifdef __has_cpp_attribute # if ! __has_cpp_attribute(maybe_unused) @@ -501,39 +539,3 @@ #else # error "__has_cpp_attribute" #endif - -#ifndef __cpp_char8_t -# error "__cpp_char8_t" -#elif __cpp_char8_t != 202207 -# error "__cpp_char8_t != 202207" -#endif - -#ifndef __cpp_designated_initializers -# error "__cpp_designated_initializers" -#elif __cpp_designated_initializers != 201707 -# error "__cpp_designated_initializers != 201707" -#endif - -#ifndef __cpp_constexpr_in_decltype -# error "__cpp_constexpr_in_decltype" -#elif __cpp_constexpr_in_decltype != 201711 -# error "__cpp_constexpr_in_decltype != 201711" -#endif - -#ifndef __cpp_consteval -# error "__cpp_consteval" -#elif __cpp_consteval != 201811 -# error "__cpp_consteval != 201811" -#endif - -#ifndef __cpp_concepts -# error "__cpp_concepts" -#elif __cpp_concepts != 202002 -# error "__cpp_concepts != 202002" -#endif - -#ifndef __cpp_using_enum -# error "__cpp_using_enum" -#elif __cpp_using_enum != 201907 -# error "__cpp_using_enum != 201907" -#endif diff --git a/gcc/testsuite/gcc.dg/attr-assume-1.c b/gcc/testsuite/gcc.dg/attr-assume-1.c new file mode 100644 index 0000000..16e919e --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-assume-1.c @@ -0,0 +1,69 @@ +/* Portable assumptions */ +/* { dg-do run } */ +/* { dg-options "-std=c2x" } */ + +int +f1 (int i) +{ + [[gnu::assume (i == 42)]]; + return i; +} + +int +f2 (int i) +{ + __attribute__ ((assume (++i == 44))); + return i; +} + +int a; +int *volatile c; + +int +f3 () +{ + ++a; + return 1; +} + +int +f4 (double x) +{ + [[gnu::assume (__builtin_isfinite (x) && x >= 0.0)]]; + return __builtin_isfinite (__builtin_sqrt (x)); +} + +double +f5 (double x) +{ + __attribute__((assume (__builtin_isfinite (__builtin_sqrt (x))))); + return __builtin_sqrt (x); +} + +int +f6 (int x) +{ + [[gnu::assume (x == 93 ? 1 : 0)]]; + return x; +} + +int +main () +{ + int b = 42; + double d = 42.0, e = 43.0; + c = &b; + [[__gnu__::__assume__ (f3 ())]]; + if (a) + __builtin_abort (); + [[gnu::assume (++b == 43)]]; + if (b != 42 || *c != 42) + __builtin_abort (); + __attribute__((assume (d < e))); + int i = 90, j = 91, k = 92; + [[gnu::__assume__ (i == 90), gnu::assume (j <= 91)]] [[gnu::assume (k >= 92)]] + ; + __attribute__((__assume__ (i == 90), assume (j <= 91))) __attribute__((assume (k >= 92))); + if (f6 (93) != 93) + __builtin_abort (); +} diff --git a/gcc/testsuite/gcc.dg/attr-assume-2.c b/gcc/testsuite/gcc.dg/attr-assume-2.c new file mode 100644 index 0000000..aa782e7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-assume-2.c @@ -0,0 +1,66 @@ +/* Portable assumptions */ +/* { dg-do compile } */ +/* { dg-options "-std=c2x" } */ + +[[gnu::__assume__ (1)]] void f1 (void); /* { dg-warning "'assume' attribute not followed by ';'" } */ + /* { dg-warning "'assume' attribute ignored" "" { target *-*-* } .-1 } */ +typedef int intx [[gnu::assume (1)]]; /* { dg-warning "'assume' attribute ignored" } */ +[[__gnu__::assume (1)]]; /* { dg-warning "'assume' attribute at top level" } */ +__attribute__((assume (1))) void f1b ();/* { dg-warning "'assume' attribute not followed by ';'" } */ + /* { dg-warning "'assume' attribute ignored" "" { target *-*-* } .-1 } */ +typedef int inty __attribute__((assume (1))); /* { dg-warning "'assume' attribute ignored" } */ + +void +foo () +{ + int i; + [[gnu::assume]]; /* { dg-error "wrong number of arguments specified for 'assume' attribute" } */ + /* { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } */ + [[gnu::__assume__ ()]]; /* { dg-error "parentheses must be omitted if attribute argument list is empty" } */ + /* { dg-error "wrong number of arguments specified for 'assume' attribute" "" { target *-*-* } .-1 } */ + /* { dg-message "expected 1, found 0" "" { target *-*-* } .-2 } */ + [[gnu::assume (1, 1)]]; /* { dg-error "wrong number of arguments specified for 'assume' attribute" } */ + /* { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } */ + [[gnu::assume (1)]] i = 1; /* { dg-warning "'assume' attribute ignored" } */ + [[gnu::assume (i = 1)]]; /* { dg-error "expected" } */ + /* { dg-warning "'assume' attribute ignored" "" { target *-*-* } .-1 } */ + __attribute__((assume)); /* { dg-error "wrong number of arguments specified for 'assume' attribute" } */ + /* { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } */ + __attribute__((assume ())); /* { dg-error "wrong number of arguments specified for 'assume' attribute" } */ + /* { dg-message "expected 1, found 0" "" { target *-*-* } .-1 } */ + __attribute__((assume (1, 1))); /* { dg-error "wrong number of arguments specified for 'assume' attribute" } */ + /* { dg-message "expected 1, found 2" "" { target *-*-* } .-1 } */ + __attribute__((assume (i = 1))); /* { dg-error "expected" } */ +} + +int +f2 (int x) +{ + __asm ("" : "+r" (x)); + return x; +} + +int +f3 (int x) +{ + [[gnu::assume (f2 (42) == 42)]]; + return x; +} + +int +f3a (int x) +{ + __attribute__((assume (f2 (42) == 42))); + return x; +} + +struct S {}; + +int +f4 () +{ + struct S s; + [[gnu::assume (s)]]; /* { dg-error "used struct type value where scalar is required" } */ + __attribute__((assume (s))); /* { dg-error "used struct type value where scalar is required" } */ + return 0; +} diff --git a/gcc/testsuite/gcc.dg/attr-assume-3.c b/gcc/testsuite/gcc.dg/attr-assume-3.c new file mode 100644 index 0000000..c611a8f --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-assume-3.c @@ -0,0 +1,35 @@ +/* Portable assumptions */ +/* { dg-do compile } */ +/* { dg-options "-std=c2x" } */ + +void +foo (int x) +{ + if (x == 1) + goto l1; /* { dg-error "jump into statement expression" } */ + else if (x == 2) + goto l2; /* { dg-error "jump into statement expression" } */ + else if (x == 3) + goto l3; /* { dg-error "jump into statement expression" } */ + [[gnu::assume (({ l0:; if (x == 0) goto l0; 1; }))]]; + [[gnu::assume (({ if (x == 0) __builtin_abort (); 1; }))]]; + [[gnu::assume (({ l1:; 1; }))]]; /* { dg-message "label 'l1' defined here" } */ + [[gnu::assume (({ l2:; 1; }))]]; /* { dg-message "label 'l2' defined here" } */ + __attribute__((assume (({ l3:; 1; })))); /* { dg-message "label 'l3' defined here" } */ + [[gnu::assume (({ l4:; 1; }))]]; /* { dg-message "label 'l4' defined here" } */ + [[gnu::assume (({ l5:; 1; }))]]; /* { dg-message "label 'l5' defined here" } */ + __attribute__((assume (({ l6:; 1; })))); /* { dg-message "label 'l6' defined here" } */ + switch (x) /* { dg-message "switch starts here" } */ + { + case 7: + [[gnu::assume (({ case 8:; 1; }))]]; /* { dg-error "switch jumps into statement expression" } */ + __attribute__((assume (({ default:; 1; })))); /* { dg-error "switch jumps into statement expression" } */ + break; + } + if (x == 4) + goto l4; /* { dg-error "jump into statement expression" } */ + else if (x == 5) + goto l5; /* { dg-error "jump into statement expression" } */ + else if (x == 6) + goto l6; /* { dg-error "jump into statement expression" } */ +} |