aboutsummaryrefslogtreecommitdiff
path: root/gcc/c/c-decl.cc
diff options
context:
space:
mode:
authorJoseph Myers <joseph@codesourcery.com>2022-11-12 17:03:21 +0000
committerJoseph Myers <joseph@codesourcery.com>2022-11-12 17:03:21 +0000
commitb556d1773db7174c71c466d9b3cafc25c7d6c825 (patch)
treeb8d88b114a9e5eb1e143544124c477da19aaa5f9 /gcc/c/c-decl.cc
parentf232715d15618e91c90eb210e23de10909590944 (diff)
downloadgcc-b556d1773db7174c71c466d9b3cafc25c7d6c825.zip
gcc-b556d1773db7174c71c466d9b3cafc25c7d6c825.tar.gz
gcc-b556d1773db7174c71c466d9b3cafc25c7d6c825.tar.bz2
c: C2x constexpr
Implement C2x constexpr (a feature based on the C++ one but much more minimal, with only constexpr variables, not functions). I believe this implementation is fully functional for use of this feature. However, there are several things that seem unclear about the specification that I'll need to raise in NB comments. There are also areas where there may be followup bug fixes because the implementation doesn't reject some more obscure cases that ought to be rejected: cases where a constexpr initializer for floating type meets the constraints for a constant expression in initializers but not those for an arithmetic constant expression (previously we haven't had to track whether something is an arithmetic constant expression in detail, unlike with integer constant expressions), and some cases where a tag or struct or union member gets declared indirectly in the declaration specifiers or declarator of a constexpr declaration, which is not permitted (modulo lack of clarity in the specification) for underspecified declarations in general (the cases of a declaration in the initializer, or a tagged type being directly declared as a type specifier, are already detected). Cases of ambiguity in the specification include: * Many questions (previously raised in WG14 discussions) over the rule about what conversions do or do not involve a change of value that's not allowed in a constexpr initializer, that aren't properly addressed by the normative text (and where the footnote on the subject isn't very clear either, and the examples don't necessarily follow from the normative text). I've made a series of choices there, that include disallowing all conversions between real and complex types or between binary and decimal floating types in constexpr initializers, that might not necessarily agree with how things end up getting clarified. The dfp.cc change also arises here, to allow quiet NaN initializers of one DFP type to be used in a constexpr initializer for another DFP type (as is possible for signaling NaNs) by ensuring the result of such a conversion is properly marked as canonical (note that most of the DFP code doesn't actually do anything with NaN payloads at all). * Various issues with what exactly counts as part of a declaration for the purposes of the rule on underspecified declarations not declaring any identifiers other than ordinary identifiers (and not declaring more than one ordinary identifier, though the latter is undefined behavior). These include cases where the declaration of a struct / union / enum type appears inside typeof or alignas in the declaration specifiers (the latter also applies with auto), or in the declarator (e.g. an array size or in a parameter declaration). The issues are similar to those involved in C90 DR#115 and C99 DRs #277 and #341; the intent may not be the same in all the different cases involved, but it's not clear that the normative wording in the various places is sufficient to deduce the differences in intent. * The wording about producing a compound literal constant using member access is present in one place but another place only applies that to named constants. * It's not clear when a structure or union constant (a constexpr variable or compound literal with structure or union type, or a member with such type extracted by a series of member access operations) can itself be used in an initializer (constexpr or otherwise). Based on general wording for initializers not having been changed, the working draft might only strictly allow it at automatic storage duration (but elsewhere it would be undefined behavior, not a constraint violation, so no diagnostic required) - since that's the only case mentioned where a single expression of structure or union type can be used to initialize an object of such a type. But it definitely seems to be allowed in even constexpr initializers at automatic storage duration - and since generally constexpr initializers (any storage duration) are *more* constrained than ordinary static storage duration initializers, it would seem odd for it not to be allowed at static storage duration. * When you do allow such initializers, it's then not entirely clear how the constraint that constexpr pointer initializers must be null pointer constants should be applied (given that a constexpr object of pointer type is a null pointer but *not* a null pointer constant). My guess would be that a constexpr struct or union containing such a field should still be allowed as an initializer, but the wording could be read otherwise. * It also becomes important with constexpr exactly what kind of constant expression an implicit zero initializer is; the wording for default initialization only really deals with the value of the initializer and not what kind of constant it is. In particular, this affects whether {} is a valid constexpr initializer for a pointer not of type void *, since the wording only talks about a null pointer, not whether it's a null pointer *constant*. I assumed that it should be a null pointer constant in that case. * It's also not entirely clear whether constexpr can be used in the declaration part of a for loop (which "shall only declare identifiers for objects having storage class auto or register"). I interpreted it as allowed (treating such objects as implicitly auto just like those with no storage class specifiers), but it could also be argued that constexpr is another storage class specifier and so not allowed there. Bootstrapped with no regressions for x86_64-pc-linux-gnu. gcc/ * dfp.cc (decimal_from_binary): Convert a canonical NaN to a canonical NaN. gcc/c-family/ * c-common.cc (c_common_reswords): Use D_C2X instead of D_CXXONLY. gcc/c/ * c-decl.cc (start_underspecified_init) (finish_underspecified_init): Handle name == NULL_TREE for compound literals. (merge_decls): Merge C_DECL_DECLARED_CONSTEXPR. (shadow_tag_warned): Check for constexpr. (start_decl): Add parameter do_push. (build_compound_literal): Set C_DECL_DECLARED_CONSTEXPR. (grokdeclarator): Handle constexpr. (finish_struct): Set C_TYPE_FIELDS_NON_CONSTEXPR. (declspecs_add_scspec): Handle constexpr. * c-parser.cc (c_token_starts_compound_literal) (c_token_starts_declspecs, c_parser_declaration_or_fndef) (c_parser_declspecs, c_parser_gnu_attribute_any_word) (c_parser_compound_literal_scspecs) (c_parser_postfix_expression_after_paren_type): Handle constexpr. Update calls to start_init. (c_parser_declaration_or_fndef, c_parser_initializer) (c_parser_initval): Pass true for new argument of convert_lvalue_to_rvalue. Call convert_lvalue_to_rvalue for constexpr compound literals. (c_parser_static_assert_declaration_no_semi) (c_parser_enum_specifier, c_parser_struct_declaration) (c_parser_alignas_specifier, c_parser_initelt, c_parser_label): Call convert_lvalue_to_rvalue on expressions required to be integer constant expressions. (c_parser_omp_declare_reduction): Update call to start_init. * c-tree.h (C_TYPE_FIELDS_NON_CONSTEXPR) (C_DECL_DECLARED_CONSTEXPR): New macros. (struct c_declspecs): Add constexpr_p. (start_decl, convert_lvalue_to_rvalue, start_init): Update prototypes. * c-typeck.cc (require_constant_value, require_constant_elements): Change to bool. (require_constexpr_value, maybe_get_constexpr_init) (constexpr_init_fits_real_type, check_constexpr_init): New. (convert_lvalue_to_rvalue): Add new parameter for_init. Call maybe_get_constexpr_init. (store_init_value): Update call to digest_init. (digest_init): Add parameters int_const_expr, arith_const_expr and require_constexpr. Check constexpr initializers. (constructor_top_level): Remove. (struct initializer_stack): Remove top_level. Add require_constexpr_value. (start_init): Remove parameter top_level. Add parameters init_require_constant and init_require_constexpr. Save require_constexpr_value on stack. (pop_init_level): Use a null pointer constant for zero initializer of pointer initialized with {}. (output_init_element): Update call to digest_init. Avoid passing null pointer constants of pointer type through digest_init a second time when initializing a constexpr object. gcc/testsuite/ * gcc.dg/c11-keywords-1.c: Also test constexpr. * gcc.dg/c2x-constexpr-1.c, gcc.dg/c2x-constexpr-2a.c, gcc.dg/c2x-constexpr-2b.c, gcc.dg/c2x-constexpr-3.c, gcc.dg/c2x-constexpr-4.c, gcc.dg/c2x-constexpr-5.c, gcc.dg/c2x-constexpr-6.c, gcc.dg/c2x-constexpr-7.c, gcc.dg/c2x-constexpr-8.c, gcc.dg/c2x-constexpr-9.c, gcc.dg/dfp/c2x-constexpr-dfp-1.c, gcc.dg/dfp/c2x-constexpr-dfp-2.c, gcc.dg/gnu2x-constexpr-1.c, gcc.target/i386/excess-precision-11.c, gcc.target/i386/excess-precision-12.c: New tests.
Diffstat (limited to 'gcc/c/c-decl.cc')
-rw-r--r--gcc/c/c-decl.cc153
1 files changed, 126 insertions, 27 deletions
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index a99b745..36de778 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -1480,26 +1480,34 @@ static bool in_underspecified_init;
means that NAME is shadowed inside its initializer, so neither the
definition being initialized, nor any definition from an outer
scope, may be referenced during that initializer. Return state to
- be passed to finish_underspecified_init. */
+ be passed to finish_underspecified_init. If NAME is NULL_TREE, the
+ underspecified object is a (constexpr) compound literal; there is
+ no shadowing in that case, but all the other restrictions on
+ underspecified object definitions still apply. */
unsigned int
start_underspecified_init (location_t loc, tree name)
{
bool prev = in_underspecified_init;
bool ok;
- tree decl = build_decl (loc, VAR_DECL, name, error_mark_node);
- C_DECL_UNDERSPECIFIED (decl) = 1;
- struct c_scope *scope = current_scope;
- struct c_binding *b = I_SYMBOL_BINDING (name);
- if (b && B_IN_SCOPE (b, scope))
- {
- error_at (loc, "underspecified declaration of %qE, which is already "
- "declared in this scope", name);
- ok = false;
- }
+ if (name == NULL_TREE)
+ ok = true;
else
{
- bind (name, decl, scope, false, false, loc);
- ok = true;
+ tree decl = build_decl (loc, VAR_DECL, name, error_mark_node);
+ C_DECL_UNDERSPECIFIED (decl) = 1;
+ struct c_scope *scope = current_scope;
+ struct c_binding *b = I_SYMBOL_BINDING (name);
+ if (b && B_IN_SCOPE (b, scope))
+ {
+ error_at (loc, "underspecified declaration of %qE, which is already "
+ "declared in this scope", name);
+ ok = false;
+ }
+ else
+ {
+ bind (name, decl, scope, false, false, loc);
+ ok = true;
+ }
}
in_underspecified_init = true;
return ok | (prev << 1);
@@ -1508,11 +1516,12 @@ start_underspecified_init (location_t loc, tree name)
/* Finish an underspecified object definition for NAME, before that
name is bound to the real declaration instead of a placeholder.
PREV_STATE is the value returned by the call to
- start_underspecified_init. */
+ start_underspecified_init. If NAME is NULL_TREE, this means a
+ compound literal, as for start_underspecified_init. */
void
finish_underspecified_init (tree name, unsigned int prev_state)
{
- if (prev_state & 1)
+ if (name != NULL_TREE && (prev_state & 1))
{
/* A VAR_DECL was bound to the name to shadow any previous
declarations for the name; remove that binding now. */
@@ -2745,6 +2754,15 @@ merge_decls (tree newdecl, tree olddecl, tree newtype, tree oldtype)
if (DECL_INITIAL (newdecl) == NULL_TREE)
DECL_INITIAL (newdecl) = DECL_INITIAL (olddecl);
+ /* Merge 'constexpr' information. */
+ if (VAR_P (olddecl) && VAR_P (newdecl))
+ {
+ if (C_DECL_DECLARED_CONSTEXPR (olddecl))
+ C_DECL_DECLARED_CONSTEXPR (newdecl) = 1;
+ else if (C_DECL_DECLARED_CONSTEXPR (newdecl))
+ C_DECL_DECLARED_CONSTEXPR (olddecl) = 1;
+ }
+
/* Merge the threadprivate attribute. */
if (VAR_P (olddecl) && C_DECL_THREADPRIVATE_P (olddecl))
C_DECL_THREADPRIVATE_P (newdecl) = 1;
@@ -4944,6 +4962,12 @@ shadow_tag_warned (const struct c_declspecs *declspecs, int warned)
warned = 1;
}
+ if (declspecs->constexpr_p)
+ {
+ error ("%<constexpr%> in empty declaration");
+ warned = 1;
+ }
+
if (current_scope == file_scope && declspecs->storage_class == csc_auto)
{
error ("%<auto%> in file-scope empty declaration");
@@ -5301,7 +5325,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
This is called as soon as the type information and variable name
have been parsed, before parsing the initializer if any.
Here we create the ..._DECL node, fill in its type,
- and put it on the list of decls for the current context.
+ and (if DO_PUSH) put it on the list of decls for the current context.
When nonnull, set *LASTLOC to the location of the prior declaration
of the same entity if one exists.
The ..._DECL node is returned as the value.
@@ -5316,7 +5340,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
tree
start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
- bool initialized, tree attributes, location_t *lastloc /* = NULL */)
+ bool initialized, tree attributes, bool do_push /* = true */,
+ location_t *lastloc /* = NULL */)
{
tree decl;
tree tem;
@@ -5489,15 +5514,20 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
/* Add this decl to the current scope.
TEM may equal DECL or it may be a previous decl of the same name. */
- tem = pushdecl (decl);
-
- if (initialized && DECL_EXTERNAL (tem))
+ if (do_push)
{
- DECL_EXTERNAL (tem) = 0;
- TREE_STATIC (tem) = 1;
- }
+ tem = pushdecl (decl);
+
+ if (initialized && DECL_EXTERNAL (tem))
+ {
+ DECL_EXTERNAL (tem) = 0;
+ TREE_STATIC (tem) = 1;
+ }
- return tem;
+ return tem;
+ }
+ else
+ return decl;
}
/* Subroutine of finish_decl. TYPE is the type of an uninitialized object
@@ -6214,6 +6244,7 @@ build_compound_literal (location_t loc, tree type, tree init, bool non_const,
DECL_ARTIFICIAL (decl) = 1;
DECL_IGNORED_P (decl) = 1;
C_DECL_COMPOUND_LITERAL_P (decl) = 1;
+ C_DECL_DECLARED_CONSTEXPR (decl) = scspecs && scspecs->constexpr_p;
TREE_TYPE (decl) = type;
if (threadp)
set_decl_tls_model (decl, decl_default_tls_model (decl));
@@ -6501,6 +6532,7 @@ grokdeclarator (const struct c_declarator *declarator,
{
tree type = declspecs->type;
bool threadp = declspecs->thread_p;
+ bool constexprp = declspecs->constexpr_p;
enum c_storage_class storage_class = declspecs->storage_class;
int constp;
int restrictp;
@@ -6743,6 +6775,7 @@ grokdeclarator (const struct c_declarator *declarator,
if (funcdef_flag
&& (threadp
+ || constexprp
|| storage_class == csc_auto
|| storage_class == csc_register
|| storage_class == csc_typedef))
@@ -6759,6 +6792,9 @@ grokdeclarator (const struct c_declarator *declarator,
error_at (loc, "function definition declared %qs",
declspecs->thread_gnu_p ? "__thread" : "_Thread_local");
threadp = false;
+ /* The parser ensures a constexpr function definition never
+ reaches here. */
+ gcc_assert (!constexprp);
if (storage_class == csc_auto
|| storage_class == csc_register
|| storage_class == csc_typedef)
@@ -6766,10 +6802,12 @@ grokdeclarator (const struct c_declarator *declarator,
}
else if (decl_context != NORMAL && (storage_class != csc_none
|| threadp
+ || constexprp
|| declspecs->c2x_auto_p))
{
if (decl_context == PARM
&& storage_class == csc_register
+ && !constexprp
&& !declspecs->c2x_auto_p)
;
else
@@ -6796,6 +6834,7 @@ grokdeclarator (const struct c_declarator *declarator,
}
storage_class = csc_none;
threadp = false;
+ constexprp = false;
}
}
else if (storage_class == csc_extern
@@ -7843,7 +7882,7 @@ grokdeclarator (const struct c_declarator *declarator,
}
else if (TREE_CODE (type) == FUNCTION_TYPE)
{
- if (storage_class == csc_register || threadp)
+ if (storage_class == csc_register || threadp || constexprp)
{
error_at (loc, "invalid storage class for function %qE", name);
}
@@ -7943,6 +7982,32 @@ grokdeclarator (const struct c_declarator *declarator,
/* An uninitialized decl with `extern' is a reference. */
int extern_ref = !initialized && storage_class == csc_extern;
+ if (constexprp)
+ {
+ /* The type of a constexpr variable must not be variably
+ modified, volatile, atomic or restrict qualified or
+ have a member with such a qualifier. const
+ qualification is implicitly added, and, at file scope,
+ has internal linkage. */
+ if (variably_modified_type_p (type, NULL_TREE))
+ error_at (loc, "%<constexpr%> object has variably modified "
+ "type");
+ if (type_quals
+ & (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
+ error_at (loc, "invalid qualifiers for %<constexpr%> object");
+ else
+ {
+ tree type_no_array = strip_array_types (type);
+ if (RECORD_OR_UNION_TYPE_P (type_no_array)
+ && C_TYPE_FIELDS_NON_CONSTEXPR (type_no_array))
+ error_at (loc, "invalid qualifiers for field of "
+ "%<constexpr%> object");
+ }
+ type_quals |= TYPE_QUAL_CONST;
+ if (current_scope == file_scope)
+ storage_class = csc_static;
+ }
+
type = c_build_qualified_type (type, type_quals, orig_qual_type,
orig_qual_indirect);
@@ -7969,6 +8034,8 @@ grokdeclarator (const struct c_declarator *declarator,
VAR_DECL, declarator->u.id.id, type);
if (size_varies)
C_DECL_VARIABLE_SIZE (decl) = 1;
+ if (constexprp)
+ C_DECL_DECLARED_CONSTEXPR (decl) = 1;
if (declspecs->inline_p)
pedwarn (loc, 0, "variable %q+D declared %<inline%>", decl);
@@ -9119,13 +9186,13 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
DECL_CONTEXT (x) = t;
+ tree t1 = strip_array_types (TREE_TYPE (x));
/* If any field is const, the structure type is pseudo-const. */
if (TREE_READONLY (x))
C_TYPE_FIELDS_READONLY (t) = 1;
else
{
/* A field that is pseudo-const makes the structure likewise. */
- tree t1 = strip_array_types (TREE_TYPE (x));
if (RECORD_OR_UNION_TYPE_P (t1) && C_TYPE_FIELDS_READONLY (t1))
C_TYPE_FIELDS_READONLY (t) = 1;
}
@@ -9133,7 +9200,18 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
/* Any field that is volatile means variables of this type must be
treated in some ways as volatile. */
if (TREE_THIS_VOLATILE (x))
- C_TYPE_FIELDS_VOLATILE (t) = 1;
+ {
+ C_TYPE_FIELDS_VOLATILE (t) = 1;
+ C_TYPE_FIELDS_NON_CONSTEXPR (t) = 1;
+ }
+
+ /* Any field that is volatile, restrict-qualified or atomic
+ means the type cannot be used for a constexpr object. */
+ if (TYPE_QUALS (t1)
+ & (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
+ C_TYPE_FIELDS_NON_CONSTEXPR (t) = 1;
+ else if (RECORD_OR_UNION_TYPE_P (t1) && C_TYPE_FIELDS_NON_CONSTEXPR (t1))
+ C_TYPE_FIELDS_NON_CONSTEXPR (t) = 1;
/* Any field of nominal variable size implies structure is too. */
if (C_DECL_VARIABLE_SIZE (x))
@@ -9335,6 +9413,7 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
TYPE_TRANSPARENT_AGGR (x) = TYPE_TRANSPARENT_AGGR (t);
C_TYPE_FIELDS_READONLY (x) = C_TYPE_FIELDS_READONLY (t);
C_TYPE_FIELDS_VOLATILE (x) = C_TYPE_FIELDS_VOLATILE (t);
+ C_TYPE_FIELDS_NON_CONSTEXPR (x) = C_TYPE_FIELDS_NON_CONSTEXPR (t);
C_TYPE_VARIABLE_SIZE (x) = C_TYPE_VARIABLE_SIZE (t);
C_TYPE_INCOMPLETE_VARS (x) = NULL_TREE;
}
@@ -12266,6 +12345,8 @@ declspecs_add_scspec (location_t loc,
error ("%qE used with %<register%>", scspec);
else if (specs->storage_class == csc_typedef)
error ("%qE used with %<typedef%>", scspec);
+ else if (specs->constexpr_p)
+ error ("%qE used with %<constexpr%>", scspec);
else
{
specs->thread_p = true;
@@ -12323,6 +12404,18 @@ declspecs_add_scspec (location_t loc,
specs->c2x_auto_p = false;
}
break;
+ case RID_CONSTEXPR:
+ dupe = specs->constexpr_p;
+ if (specs->storage_class == csc_extern)
+ error ("%qE used with %<extern%>", scspec);
+ else if (specs->storage_class == csc_typedef)
+ error ("%qE used with %<typedef%>", scspec);
+ else if (specs->thread_p)
+ error ("%qE used with %qs", scspec,
+ specs->thread_gnu_p ? "__thread" : "_Thread_local");
+ else
+ specs->constexpr_p = true;
+ break;
default:
gcc_unreachable ();
}
@@ -12352,6 +12445,12 @@ declspecs_add_scspec (location_t loc,
scspec);
specs->thread_p = false;
}
+ if (n != csc_auto && n != csc_register && n != csc_static
+ && specs->constexpr_p)
+ {
+ error ("%<constexpr%> used with %qE", scspec);
+ specs->constexpr_p = false;
+ }
}
}
return specs;