diff options
Diffstat (limited to 'gcc/rust/backend/rust-constexpr.cc')
-rw-r--r-- | gcc/rust/backend/rust-constexpr.cc | 1329 |
1 files changed, 1321 insertions, 8 deletions
diff --git a/gcc/rust/backend/rust-constexpr.cc b/gcc/rust/backend/rust-constexpr.cc index aa2d470..fef1de8 100644 --- a/gcc/rust/backend/rust-constexpr.cc +++ b/gcc/rust/backend/rust-constexpr.cc @@ -28,6 +28,8 @@ #include "timevar.h" #include "varasm.h" #include "cgraph.h" +#include "tree-inline.h" +#include "vec.h" #define VERIFY_CONSTANT(X) \ do \ @@ -40,6 +42,29 @@ namespace Rust { namespace Compile { +/* Returns true iff FUN is an instantiation of a constexpr function + template or a defaulted constexpr function. */ + +bool +is_instantiation_of_constexpr (tree fun) +{ + return DECL_DECLARED_CONSTEXPR_P (fun); +} + +/* Return true if T is a literal type. */ + +bool +literal_type_p (tree t) +{ + if (SCALAR_TYPE_P (t) || VECTOR_TYPE_P (t) || TYPE_REF_P (t) + || (VOID_TYPE_P (t))) + return true; + + if (TREE_CODE (t) == ARRAY_TYPE) + return literal_type_p (strip_array_types (t)); + return false; +} + static bool verify_constant (tree, bool, bool *, bool *); @@ -49,6 +74,12 @@ static int array_index_cmp (tree key, tree index); inline tree get_nth_callarg (tree t, int n); +tree +unshare_constructor (tree t MEM_STAT_DECL); +void +explain_invalid_constexpr_fn (tree fun); +void +maybe_save_constexpr_fundef (tree fun); struct constexpr_global_ctx { @@ -156,6 +187,268 @@ struct constexpr_ctx bool manifestly_const_eval; }; +struct constexpr_fundef_hasher : ggc_ptr_hash<constexpr_fundef> +{ + static hashval_t hash (const constexpr_fundef *); + static bool equal (const constexpr_fundef *, const constexpr_fundef *); +}; + +/* This table holds all constexpr function definitions seen in + the current translation unit. */ + +static GTY (()) hash_table<constexpr_fundef_hasher> *constexpr_fundef_table; + +/* Utility function used for managing the constexpr function table. + Return true if the entries pointed to by P and Q are for the + same constexpr function. */ + +inline bool +constexpr_fundef_hasher::equal (const constexpr_fundef *lhs, + const constexpr_fundef *rhs) +{ + return lhs->decl == rhs->decl; +} + +/* Utility function used for managing the constexpr function table. + Return a hash value for the entry pointed to by Q. */ + +inline hashval_t +constexpr_fundef_hasher::hash (const constexpr_fundef *fundef) +{ + return DECL_UID (fundef->decl); +} + +/* Return a previously saved definition of function FUN. */ + +constexpr_fundef * +retrieve_constexpr_fundef (tree fun) +{ + if (constexpr_fundef_table == NULL) + return NULL; + + constexpr_fundef fundef = {fun, NULL_TREE, NULL_TREE, NULL_TREE}; + return constexpr_fundef_table->find (&fundef); +} + +/* This internal flag controls whether we should avoid doing anything during + constexpr evaluation that would cause extra DECL_UID generation, such as + template instantiation and function body copying. */ + +static bool uid_sensitive_constexpr_evaluation_value; + +/* An internal counter that keeps track of the number of times + uid_sensitive_constexpr_evaluation_p returned true. */ + +static unsigned uid_sensitive_constexpr_evaluation_true_counter; + +/* The accessor for uid_sensitive_constexpr_evaluation_value which also + increments the corresponding counter. */ + +static bool +uid_sensitive_constexpr_evaluation_p () +{ + if (uid_sensitive_constexpr_evaluation_value) + { + ++uid_sensitive_constexpr_evaluation_true_counter; + return true; + } + else + return false; +} + +/* RAII sentinel that saves the value of a variable, optionally + overrides it right away, and restores its value when the sentinel + id destructed. */ + +template <typename T> class temp_override +{ + T &overridden_variable; + T saved_value; + +public: + temp_override (T &var) : overridden_variable (var), saved_value (var) {} + temp_override (T &var, T overrider) + : overridden_variable (var), saved_value (var) + { + overridden_variable = overrider; + } + ~temp_override () { overridden_variable = saved_value; } +}; + +/* An RAII sentinel used to restrict constexpr evaluation so that it + doesn't do anything that causes extra DECL_UID generation. */ + +struct uid_sensitive_constexpr_evaluation_sentinel +{ + temp_override<bool> ovr; + uid_sensitive_constexpr_evaluation_sentinel (); +}; + +/* Used to determine whether uid_sensitive_constexpr_evaluation_p was + called and returned true, indicating that we've restricted constexpr + evaluation in order to avoid UID generation. We use this to control + updates to the fold_cache and cv_cache. */ + +struct uid_sensitive_constexpr_evaluation_checker +{ + const unsigned saved_counter; + uid_sensitive_constexpr_evaluation_checker (); + bool evaluation_restricted_p () const; +}; + +/* The default constructor for uid_sensitive_constexpr_evaluation_sentinel + enables the internal flag for uid_sensitive_constexpr_evaluation_p + during the lifetime of the sentinel object. Upon its destruction, the + previous value of uid_sensitive_constexpr_evaluation_p is restored. */ + +uid_sensitive_constexpr_evaluation_sentinel :: + uid_sensitive_constexpr_evaluation_sentinel () + : ovr (uid_sensitive_constexpr_evaluation_value, true) +{} + +/* The default constructor for uid_sensitive_constexpr_evaluation_checker + records the current number of times that uid_sensitive_constexpr_evaluation_p + has been called and returned true. */ + +uid_sensitive_constexpr_evaluation_checker :: + uid_sensitive_constexpr_evaluation_checker () + : saved_counter (uid_sensitive_constexpr_evaluation_true_counter) +{} + +/* Returns true iff uid_sensitive_constexpr_evaluation_p is true, and + some constexpr evaluation was restricted due to u_s_c_e_p being called + and returning true during the lifetime of this checker object. */ + +bool +uid_sensitive_constexpr_evaluation_checker::evaluation_restricted_p () const +{ + return (uid_sensitive_constexpr_evaluation_value + && saved_counter != uid_sensitive_constexpr_evaluation_true_counter); +} + +/* A table of all constexpr calls that have been evaluated by the + compiler in this translation unit. */ + +static GTY (()) hash_table<constexpr_call_hasher> *constexpr_call_table; + +static tree +cxx_eval_constant_expression (const constexpr_ctx *, tree, bool, bool *, bool *, + tree * = NULL); + +/* Compute a hash value for a constexpr call representation. */ + +inline hashval_t +constexpr_call_hasher::hash (constexpr_call *info) +{ + return info->hash; +} + +/* Return true if the objects pointed to by P and Q represent calls + to the same constexpr function with the same arguments. + Otherwise, return false. */ + +bool +constexpr_call_hasher::equal (constexpr_call *lhs, constexpr_call *rhs) +{ + if (lhs == rhs) + return true; + if (lhs->hash != rhs->hash) + return false; + if (lhs->manifestly_const_eval != rhs->manifestly_const_eval) + return false; + if (!constexpr_fundef_hasher::equal (lhs->fundef, rhs->fundef)) + return false; + return rs_tree_equal (lhs->bindings, rhs->bindings); +} + +/* Initialize the constexpr call table, if needed. */ + +static void +maybe_initialize_constexpr_call_table (void) +{ + if (constexpr_call_table == NULL) + constexpr_call_table = hash_table<constexpr_call_hasher>::create_ggc (101); +} + +/* During constexpr CALL_EXPR evaluation, to avoid issues with sharing when + a function happens to get called recursively, we unshare the callee + function's body and evaluate this unshared copy instead of evaluating the + original body. + + FUNDEF_COPIES_TABLE is a per-function freelist of these unshared function + copies. The underlying data structure of FUNDEF_COPIES_TABLE is a hash_map + that's keyed off of the original FUNCTION_DECL and whose value is a + TREE_LIST of this function's unused copies awaiting reuse. + + This is not GC-deletable to avoid GC affecting UID generation. */ + +static GTY (()) decl_tree_map *fundef_copies_table; + +/* Reuse a copy or create a new unshared copy of the function FUN. + Return this copy. We use a TREE_LIST whose PURPOSE is body, VALUE + is parms, TYPE is result. */ + +static tree +get_fundef_copy (constexpr_fundef *fundef) +{ + tree copy; + bool existed; + tree *slot + = &(hash_map_safe_get_or_insert<hm_ggc> (fundef_copies_table, fundef->decl, + &existed, 127)); + + if (!existed) + { + /* There is no cached function available, or in use. We can use + the function directly. That the slot is now created records + that this function is now in use. */ + copy = build_tree_list (fundef->body, fundef->parms); + TREE_TYPE (copy) = fundef->result; + } + else if (*slot == NULL_TREE) + { + if (uid_sensitive_constexpr_evaluation_p ()) + return NULL_TREE; + + /* We've already used the function itself, so make a copy. */ + copy = build_tree_list (NULL, NULL); + tree saved_body = DECL_SAVED_TREE (fundef->decl); + tree saved_parms = DECL_ARGUMENTS (fundef->decl); + tree saved_result = DECL_RESULT (fundef->decl); + tree saved_fn = current_function_decl; + DECL_SAVED_TREE (fundef->decl) = fundef->body; + DECL_ARGUMENTS (fundef->decl) = fundef->parms; + DECL_RESULT (fundef->decl) = fundef->result; + current_function_decl = fundef->decl; + TREE_PURPOSE (copy) + = copy_fn (fundef->decl, TREE_VALUE (copy), TREE_TYPE (copy)); + current_function_decl = saved_fn; + DECL_RESULT (fundef->decl) = saved_result; + DECL_ARGUMENTS (fundef->decl) = saved_parms; + DECL_SAVED_TREE (fundef->decl) = saved_body; + } + else + { + /* We have a cached function available. */ + copy = *slot; + *slot = TREE_CHAIN (copy); + } + + return copy; +} + +/* Save the copy COPY of function FUN for later reuse by + get_fundef_copy(). By construction, there will always be an entry + to find. */ + +static void +save_fundef_copy (tree fun, tree copy) +{ + tree *slot = fundef_copies_table->get (fun); + TREE_CHAIN (copy) = *slot; + *slot = copy; +} + static tree constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p, bool unshare_p); @@ -176,7 +469,7 @@ static tree eval_store_expression (const constexpr_ctx *ctx, tree r, bool, bool *, bool *); static tree -eval_call_expression (const constexpr_ctx *ctx, tree r); +eval_call_expression (const constexpr_ctx *ctx, tree r, bool, bool *, bool *); static tree eval_binary_expression (const constexpr_ctx *ctx, tree r, bool, bool *, bool *); @@ -184,6 +477,50 @@ eval_binary_expression (const constexpr_ctx *ctx, tree r, bool, bool *, bool *); static tree get_function_named_in_call (tree t); +static tree +eval_statement_list (const constexpr_ctx *ctx, tree t, bool *non_constant_p, + bool *overflow_p, tree *jump_target); +static tree +extract_string_elt (tree string, unsigned chars_per_elt, unsigned index); + +/* Variables and functions to manage constexpr call expansion context. + These do not need to be marked for PCH or GC. */ + +/* FIXME remember and print actual constant arguments. */ +static vec<tree> call_stack; +static int call_stack_tick; +static int last_cx_error_tick; + +static int +push_cx_call_context (tree call) +{ + ++call_stack_tick; + if (!EXPR_HAS_LOCATION (call)) + SET_EXPR_LOCATION (call, input_location); + call_stack.safe_push (call); + int len = call_stack.length (); + if (len > max_constexpr_depth) + return false; + return len; +} + +static void +pop_cx_call_context (void) +{ + ++call_stack_tick; + call_stack.pop (); +} + +vec<tree> +cx_error_context (void) +{ + vec<tree> r = vNULL; + if (call_stack_tick != last_cx_error_tick && !call_stack.is_empty ()) + r = call_stack; + last_cx_error_tick = call_stack_tick; + return r; +} + // this is ported from cxx_eval_outermost_constant_expr tree fold_expr (tree expr) @@ -262,6 +599,28 @@ constexpr_expression (const constexpr_ctx *ctx, tree t, bool lval, } break; + case PARM_DECL: + if (lval && !TYPE_REF_P (TREE_TYPE (t))) + /* glvalue use. */; + else if (tree *p = ctx->global->values.get (r)) + r = *p; + else if (lval) + /* Defer in case this is only used for its type. */; + else if (COMPLETE_TYPE_P (TREE_TYPE (t)) + && is_really_empty_class (TREE_TYPE (t), /*ignore_vptr*/ false)) + { + /* If the class is empty, we aren't actually loading anything. */ + r = build_constructor (TREE_TYPE (t), NULL); + TREE_CONSTANT (r) = true; + } + else + { + if (!ctx->quiet) + error ("%qE is not a constant expression", t); + *non_constant_p = true; + } + break; + case POINTER_PLUS_EXPR: case POINTER_DIFF_EXPR: case PLUS_EXPR: @@ -307,7 +666,7 @@ constexpr_expression (const constexpr_ctx *ctx, tree t, bool lval, break; case CALL_EXPR: - r = eval_call_expression (ctx, t); + r = eval_call_expression (ctx, t, false, non_constant_p, overflow_p); break; case RETURN_EXPR: @@ -320,6 +679,48 @@ constexpr_expression (const constexpr_ctx *ctx, tree t, bool lval, r = eval_store_expression (ctx, t, false, non_constant_p, overflow_p); break; + case STATEMENT_LIST: + // new_ctx = *ctx; + // new_ctx.ctor = new_ctx.object = NULL_TREE; + return eval_statement_list (ctx, t, non_constant_p, overflow_p, + jump_target); + + case BIND_EXPR: + return constexpr_expression (ctx, BIND_EXPR_BODY (t), lval, + non_constant_p, overflow_p, jump_target); + + case RESULT_DECL: + if (lval) + return t; + /* We ask for an rvalue for the RESULT_DECL when indirecting + through an invisible reference, or in named return value + optimization. */ + if (tree *p = ctx->global->values.get (t)) + return *p; + else + { + if (!ctx->quiet) + error ("%qE is not a constant expression", t); + *non_constant_p = true; + } + break; + + case SAVE_EXPR: + /* Avoid evaluating a SAVE_EXPR more than once. */ + if (tree *p = ctx->global->values.get (t)) + r = *p; + else + { + r = constexpr_expression (ctx, TREE_OPERAND (t, 0), false, + non_constant_p, overflow_p); + if (*non_constant_p) + break; + ctx->global->values.put (t, r); + if (ctx->save_exprs) + ctx->save_exprs->safe_push (t); + } + break; + default: break; } @@ -327,10 +728,152 @@ constexpr_expression (const constexpr_ctx *ctx, tree t, bool lval, return r; } +/* Return a pointer to the constructor_elt of CTOR which matches INDEX. If no + matching constructor_elt exists, then add one to CTOR. + + As an optimization, if POS_HINT is non-negative then it is used as a guess + for the (integer) index of the matching constructor_elt within CTOR. */ + +static constructor_elt * +get_or_insert_ctor_field (tree ctor, tree index, int pos_hint = -1) +{ + /* Check the hint first. */ + if (pos_hint >= 0 && (unsigned) pos_hint < CONSTRUCTOR_NELTS (ctor) + && CONSTRUCTOR_ELT (ctor, pos_hint)->index == index) + return CONSTRUCTOR_ELT (ctor, pos_hint); + + tree type = TREE_TYPE (ctor); + if (TREE_CODE (type) == VECTOR_TYPE && index == NULL_TREE) + { + CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (ctor), index, NULL_TREE); + return &CONSTRUCTOR_ELTS (ctor)->last (); + } + else if (TREE_CODE (type) == ARRAY_TYPE || TREE_CODE (type) == VECTOR_TYPE) + { + if (TREE_CODE (index) == RANGE_EXPR) + { + /* Support for RANGE_EXPR index lookups is currently limited to + accessing an existing element via POS_HINT, or appending a new + element to the end of CTOR. ??? Support for other access + patterns may also be needed. */ + vec<constructor_elt, va_gc> *elts = CONSTRUCTOR_ELTS (ctor); + if (vec_safe_length (elts)) + { + tree lo = TREE_OPERAND (index, 0); + gcc_assert (array_index_cmp (elts->last ().index, lo) < 0); + } + CONSTRUCTOR_APPEND_ELT (elts, index, NULL_TREE); + return &elts->last (); + } + + HOST_WIDE_INT i = find_array_ctor_elt (ctor, index, /*insert*/ true); + gcc_assert (i >= 0); + constructor_elt *cep = CONSTRUCTOR_ELT (ctor, i); + gcc_assert (cep->index == NULL_TREE + || TREE_CODE (cep->index) != RANGE_EXPR); + return cep; + } + else + { + gcc_assert ( + TREE_CODE (index) == FIELD_DECL + && (same_type_ignoring_top_level_qualifiers_p (DECL_CONTEXT (index), + TREE_TYPE (ctor)))); + + /* We must keep the CONSTRUCTOR's ELTS in FIELD order. + Usually we meet initializers in that order, but it is + possible for base types to be placed not in program + order. */ + tree fields = TYPE_FIELDS (DECL_CONTEXT (index)); + unsigned HOST_WIDE_INT idx = 0; + constructor_elt *cep = NULL; + + /* Check if we're changing the active member of a union. */ + if (TREE_CODE (type) == UNION_TYPE && CONSTRUCTOR_NELTS (ctor) + && CONSTRUCTOR_ELT (ctor, 0)->index != index) + vec_safe_truncate (CONSTRUCTOR_ELTS (ctor), 0); + /* If the bit offset of INDEX is larger than that of the last + constructor_elt, then we can just immediately append a new + constructor_elt to the end of CTOR. */ + else if (CONSTRUCTOR_NELTS (ctor) + && tree_int_cst_compare ( + bit_position (index), + bit_position (CONSTRUCTOR_ELTS (ctor)->last ().index)) + > 0) + { + idx = CONSTRUCTOR_NELTS (ctor); + goto insert; + } + + /* Otherwise, we need to iterate over CTOR to find or insert INDEX + appropriately. */ + + for (; vec_safe_iterate (CONSTRUCTOR_ELTS (ctor), idx, &cep); + idx++, fields = DECL_CHAIN (fields)) + { + if (index == cep->index) + goto found; + + /* The field we're initializing must be on the field + list. Look to see if it is present before the + field the current ELT initializes. */ + for (; fields != cep->index; fields = DECL_CHAIN (fields)) + if (index == fields) + goto insert; + } + /* We fell off the end of the CONSTRUCTOR, so insert a new + entry at the end. */ + + insert : { + constructor_elt ce = {index, NULL_TREE}; + + vec_safe_insert (CONSTRUCTOR_ELTS (ctor), idx, ce); + cep = CONSTRUCTOR_ELT (ctor, idx); + } + found:; + + return cep; + } +} + +/* Complain about a const object OBJ being modified in a constant expression. + EXPR is the MODIFY_EXPR expression performing the modification. */ + +static void +modifying_const_object_error (tree expr, tree obj) +{ + location_t loc = EXPR_LOCATION (expr); + auto_diagnostic_group d; + error_at (loc, + "modifying a const object %qE is not allowed in " + "a constant expression", + TREE_OPERAND (expr, 0)); + inform (location_of (obj), "originally declared %<const%> here"); +} + +/* Return true iff DECL is an empty field, either for an empty base or a + [[no_unique_address]] data member. */ + +bool +is_empty_field (tree decl) +{ + if (!decl || TREE_CODE (decl) != FIELD_DECL) + return false; + + bool r = is_empty_class (TREE_TYPE (decl)); + + /* Empty fields should have size zero. */ + gcc_checking_assert (!r || integer_zerop (DECL_SIZE (decl))); + + return r; +} + static tree eval_store_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p) { + constexpr_ctx new_ctx = *ctx; + tree init = TREE_OPERAND (t, 1); if (TREE_CLOBBER_P (init)) /* Just ignore clobbers. */ @@ -345,29 +888,356 @@ eval_store_expression (const constexpr_ctx *ctx, tree t, bool lval, { /* Evaluate the value to be stored without knowing what object it will be stored in, so that any side-effects happen first. */ - init = fold_expr (init); + if (!SCALAR_TYPE_P (type)) + new_ctx.ctor = new_ctx.object = NULL_TREE; + init = constexpr_expression (&new_ctx, init, false, non_constant_p, + overflow_p); + if (*non_constant_p) + return t; } bool evaluated = false; + if (lval) + { + /* If we want to return a reference to the target, we need to evaluate it + as a whole; otherwise, only evaluate the innermost piece to avoid + building up unnecessary *_REFs. */ + target + = constexpr_expression (ctx, target, true, non_constant_p, overflow_p); + evaluated = true; + if (*non_constant_p) + return t; + } + + /* Find the underlying variable. */ + releasing_vec refs; tree object = NULL_TREE; + /* If we're modifying a const object, save it. */ + tree const_object_being_modified = NULL_TREE; + bool mutable_p = false; for (tree probe = target; object == NULL_TREE;) { switch (TREE_CODE (probe)) { + case BIT_FIELD_REF: + case COMPONENT_REF: + case ARRAY_REF: { + tree ob = TREE_OPERAND (probe, 0); + tree elt = TREE_OPERAND (probe, 1); + if (TREE_CODE (elt) == FIELD_DECL /*&& DECL_MUTABLE_P (elt)*/) + mutable_p = true; + if (TREE_CODE (probe) == ARRAY_REF) + { + // TODO + gcc_unreachable (); + // elt = eval_and_check_array_index (ctx, probe, false, + // non_constant_p, overflow_p); + if (*non_constant_p) + return t; + } + /* We don't check modifying_const_object_p for ARRAY_REFs. Given + "int a[10]", an ARRAY_REF "a[2]" can be "const int", even though + the array isn't const. Instead, check "a" in the next iteration; + that will detect modifying "const int a[10]". */ + // else if (evaluated + // && modifying_const_object_p (TREE_CODE (t), probe, + // mutable_p) + // && const_object_being_modified == NULL_TREE) + // const_object_being_modified = probe; + vec_safe_push (refs, elt); + vec_safe_push (refs, TREE_TYPE (probe)); + probe = ob; + } + break; + default: if (evaluated) object = probe; else { - probe = constexpr_expression (ctx, probe, lval, non_constant_p, + probe = constexpr_expression (ctx, probe, true, non_constant_p, overflow_p); evaluated = true; + if (*non_constant_p) + return t; } break; } } - return init; + // if (modifying_const_object_p (TREE_CODE (t), object, mutable_p) + // && const_object_being_modified == NULL_TREE) + // const_object_being_modified = object; + + /* And then find/build up our initializer for the path to the subobject + we're initializing. */ + tree *valp; + if (DECL_P (object)) + valp = ctx->global->values.get (object); + else + valp = NULL; + if (!valp) + { + /* A constant-expression cannot modify objects from outside the + constant-expression. */ + if (!ctx->quiet) + error ("modification of %qE is not a constant expression", object); + *non_constant_p = true; + return t; + } + type = TREE_TYPE (object); + bool no_zero_init = true; + + releasing_vec ctors, indexes; + auto_vec<int> index_pos_hints; + bool activated_union_member_p = false; + while (!refs->is_empty ()) + { + if (*valp == NULL_TREE) + { + *valp = build_constructor (type, NULL); + CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init; + } + else if (TREE_CODE (*valp) == STRING_CST) + { + /* An array was initialized with a string constant, and now + we're writing into one of its elements. Explode the + single initialization into a set of element + initializations. */ + gcc_assert (TREE_CODE (type) == ARRAY_TYPE); + + tree string = *valp; + tree elt_type = TREE_TYPE (type); + unsigned chars_per_elt + = (TYPE_PRECISION (elt_type) / TYPE_PRECISION (char_type_node)); + unsigned num_elts = TREE_STRING_LENGTH (string) / chars_per_elt; + tree ary_ctor = build_constructor (type, NULL); + + vec_safe_reserve (CONSTRUCTOR_ELTS (ary_ctor), num_elts); + for (unsigned ix = 0; ix != num_elts; ix++) + { + constructor_elt elt + = {build_int_cst (size_type_node, ix), + extract_string_elt (string, chars_per_elt, ix)}; + CONSTRUCTOR_ELTS (ary_ctor)->quick_push (elt); + } + + *valp = ary_ctor; + } + + /* If the value of object is already zero-initialized, any new ctors for + subobjects will also be zero-initialized. */ + no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp); + + enum tree_code code = TREE_CODE (type); + type = refs->pop (); + tree index = refs->pop (); + + if (code == RECORD_TYPE && is_empty_field (index)) + /* Don't build a sub-CONSTRUCTOR for an empty base or field, as they + have no data and might have an offset lower than previously declared + fields, which confuses the middle-end. The code below will notice + that we don't have a CONSTRUCTOR for our inner target and just + return init. */ + break; + + if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp) + && CONSTRUCTOR_ELT (*valp, 0)->index != index) + { + if (TREE_CODE (t) == MODIFY_EXPR && CONSTRUCTOR_NO_CLEARING (*valp)) + { + /* Diagnose changing the active union member while the union + is in the process of being initialized. */ + if (!ctx->quiet) + error_at (EXPR_LOCATION (t), + "change of the active member of a union " + "from %qD to %qD during initialization", + CONSTRUCTOR_ELT (*valp, 0)->index, index); + *non_constant_p = true; + } + no_zero_init = true; + } + + vec_safe_push (ctors, *valp); + vec_safe_push (indexes, index); + + constructor_elt *cep = get_or_insert_ctor_field (*valp, index); + index_pos_hints.safe_push (cep - CONSTRUCTOR_ELTS (*valp)->begin ()); + + if (code == UNION_TYPE) + activated_union_member_p = true; + + valp = &cep->value; + } + + /* Detect modifying a constant object in constexpr evaluation. + We have found a const object that is being modified. Figure out + if we need to issue an error. Consider + + struct A { + int n; + constexpr A() : n(1) { n = 2; } // #1 + }; + struct B { + const A a; + constexpr B() { a.n = 3; } // #2 + }; + constexpr B b{}; + + #1 is OK, since we're modifying an object under construction, but + #2 is wrong, since "a" is const and has been fully constructed. + To track it, we use the TREE_READONLY bit in the object's CONSTRUCTOR + which means that the object is read-only. For the example above, the + *ctors stack at the point of #2 will look like: + + ctors[0] = {.a={.n=2}} TREE_READONLY = 0 + ctors[1] = {.n=2} TREE_READONLY = 1 + + and we're modifying "b.a", so we search the stack and see if the + constructor for "b.a" has already run. */ + if (const_object_being_modified) + { + bool fail = false; + tree const_objtype + = strip_array_types (TREE_TYPE (const_object_being_modified)); + if (!CLASS_TYPE_P (const_objtype)) + fail = true; + else + { + /* [class.ctor]p5 "A constructor can be invoked for a const, + volatile, or const volatile object. const and volatile + semantics are not applied on an object under construction. + They come into effect when the constructor for the most + derived object ends." */ + for (tree elt : *ctors) + if (same_type_ignoring_top_level_qualifiers_p ( + TREE_TYPE (const_object_being_modified), TREE_TYPE (elt))) + { + fail = TREE_READONLY (elt); + break; + } + } + if (fail) + { + if (!ctx->quiet) + modifying_const_object_error (t, const_object_being_modified); + *non_constant_p = true; + return t; + } + } + + if (!preeval) + { + /* We're handling an INIT_EXPR of class type, so the value of the + initializer can depend on the object it's initializing. */ + + /* Create a new CONSTRUCTOR in case evaluation of the initializer + wants to modify it. */ + if (*valp == NULL_TREE) + { + *valp = build_constructor (type, NULL); + CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init; + } + new_ctx.ctor = *valp; + new_ctx.object = target; + /* Avoid temporary materialization when initializing from a TARGET_EXPR. + We don't need to mess with AGGR_EXPR_SLOT/VEC_INIT_EXPR_SLOT because + expansion of those trees uses ctx instead. */ + if (TREE_CODE (init) == TARGET_EXPR) + if (tree tinit = TARGET_EXPR_INITIAL (init)) + init = tinit; + init = constexpr_expression (&new_ctx, init, false, non_constant_p, + overflow_p); + /* The hash table might have moved since the get earlier, and the + initializer might have mutated the underlying CONSTRUCTORs, so we must + recompute VALP. */ + valp = ctx->global->values.get (object); + for (unsigned i = 0; i < vec_safe_length (indexes); i++) + { + constructor_elt *cep + = get_or_insert_ctor_field (*valp, indexes[i], index_pos_hints[i]); + valp = &cep->value; + } + } + + /* Don't share a CONSTRUCTOR that might be changed later. */ + init = unshare_constructor (init); + + if (*valp && TREE_CODE (*valp) == CONSTRUCTOR + && TREE_CODE (init) == CONSTRUCTOR) + { + /* An outer ctx->ctor might be pointing to *valp, so replace + its contents. */ + if (!same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (init), + TREE_TYPE (*valp))) + { + /* For initialization of an empty base, the original target will be + *(base*)this, evaluation of which resolves to the object + argument, which has the derived type rather than the base type. In + this situation, just evaluate the initializer and return, since + there's no actual data to store. */ + gcc_assert (is_empty_class (TREE_TYPE (init))); + return lval ? target : init; + } + CONSTRUCTOR_ELTS (*valp) = CONSTRUCTOR_ELTS (init); + TREE_CONSTANT (*valp) = TREE_CONSTANT (init); + TREE_SIDE_EFFECTS (*valp) = TREE_SIDE_EFFECTS (init); + CONSTRUCTOR_NO_CLEARING (*valp) = CONSTRUCTOR_NO_CLEARING (init); + } + else if (TREE_CODE (init) == CONSTRUCTOR + && !same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (init), + type)) + { + /* See above on initialization of empty bases. */ + gcc_assert (is_empty_class (TREE_TYPE (init)) && !lval); + return init; + } + else + *valp = init; + + /* After initialization, 'const' semantics apply to the value of the + object. Make a note of this fact by marking the CONSTRUCTOR + TREE_READONLY. */ + if (TREE_CODE (t) == INIT_EXPR && TREE_CODE (*valp) == CONSTRUCTOR + && TYPE_READONLY (type)) + { + // this vs self? can rust's self be anything other than self or &self in + // constexpr mode? if (INDIRECT_REF_P (target) + // && (is_this_parameter ( + // tree_strip_nop_conversions (TREE_OPERAND (target, 0))))) + /* We've just initialized '*this' (perhaps via the target + constructor of a delegating constructor). Leave it up to the + caller that set 'this' to set TREE_READONLY appropriately. */ + // gcc_checking_assert ( + // same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (target), + // type)); + // else + // TREE_READONLY (*valp) = true; + } + + /* Update TREE_CONSTANT and TREE_SIDE_EFFECTS on enclosing + CONSTRUCTORs, if any. */ + bool c = TREE_CONSTANT (init); + bool s = TREE_SIDE_EFFECTS (init); + if (!c || s || activated_union_member_p) + for (tree elt : *ctors) + { + if (!c) + TREE_CONSTANT (elt) = false; + if (s) + TREE_SIDE_EFFECTS (elt) = true; + /* Clear CONSTRUCTOR_NO_CLEARING since we've activated a member of + this union. */ + if (TREE_CODE (TREE_TYPE (elt)) == UNION_TYPE) + CONSTRUCTOR_NO_CLEARING (elt) = false; + } + + if (*non_constant_p) + return t; + else if (lval) + return target; + else + return init; } /* Subroutine of cxx_eval_constant_expression. @@ -526,10 +1396,290 @@ rs_bind_parameters_in_call (const constexpr_ctx *ctx, tree t, tree fun, // Evaluate the call expression tree T in the context of OLD_CALL expression // evaluation. static tree -eval_call_expression (const constexpr_ctx *ctx, tree t) +eval_call_expression (const constexpr_ctx *ctx, tree t, bool lval, + bool *non_constant_p, bool *overflow_p) { + location_t loc = EXPR_LOCATION (t); tree fun = get_function_named_in_call (t); - return constexpr_fn_retval (ctx, DECL_SAVED_TREE (fun)); + constexpr_call new_call = {NULL, NULL, NULL, 0, ctx->manifestly_const_eval}; + int depth_ok; + + if (fun == NULL_TREE) + { + // return cxx_eval_internal_function (ctx, t, lval, + // non_constant_p, overflow_p); + gcc_unreachable (); + return error_mark_node; + } + + if (TREE_CODE (fun) != FUNCTION_DECL) + { + if (!ctx->quiet && !*non_constant_p) + error_at (loc, + "expression %qE does not designate a %<constexpr%> " + "function", + fun); + *non_constant_p = true; + return t; + } + + bool non_constant_args = false; + new_call.bindings + = rs_bind_parameters_in_call (ctx, t, fun, non_constant_p, overflow_p, + &non_constant_args); + + /* We build up the bindings list before we know whether we already have this + call cached. If we don't end up saving these bindings, ggc_free them when + this function exits. */ + class free_bindings + { + tree *bindings; + + public: + free_bindings (tree &b) : bindings (&b) {} + ~free_bindings () + { + if (bindings) + ggc_free (*bindings); + } + void preserve () { bindings = NULL; } + } fb (new_call.bindings); + + if (*non_constant_p) + return t; + + /* If in direct recursive call, optimize definition search. */ + if (ctx && ctx->call && ctx->call->fundef && ctx->call->fundef->decl == fun) + new_call.fundef = ctx->call->fundef; + else + { + new_call.fundef = retrieve_constexpr_fundef (fun); + if (new_call.fundef == NULL || new_call.fundef->body == NULL + || new_call.fundef->result == error_mark_node + || fun == current_function_decl) + { + if (!ctx->quiet) + { + /* We need to check for current_function_decl here in case we're + being called during cp_fold_function, because at that point + DECL_INITIAL is set properly and we have a fundef but we + haven't lowered invisirefs yet (c++/70344). */ + if (DECL_INITIAL (fun) == error_mark_node + || fun == current_function_decl) + error_at (loc, + "%qD called in a constant expression before its " + "definition is complete", + fun); + else if (DECL_INITIAL (fun)) + { + // /* The definition of fun was somehow unsuitable. But + // pretend + // that lambda static thunks don't exist. */ + // if (!lambda_static_thunk_p (fun)) + // error_at (loc, "%qD called in a constant expression", + // fun); + explain_invalid_constexpr_fn (fun); + } + else + error_at (loc, "%qD used before its definition", fun); + } + *non_constant_p = true; + return t; + } + } + + depth_ok = push_cx_call_context (t); + + tree result = NULL_TREE; + constexpr_call *entry = NULL; + if (depth_ok && !non_constant_args && ctx->strict) + { + new_call.hash = constexpr_fundef_hasher::hash (new_call.fundef); + // new_call.hash + // = iterative_hash_template_arg (new_call.bindings, new_call.hash); + new_call.hash + = iterative_hash_object (ctx->manifestly_const_eval, new_call.hash); + + /* If we have seen this call before, we are done. */ + maybe_initialize_constexpr_call_table (); + constexpr_call **slot + = constexpr_call_table->find_slot (&new_call, INSERT); + entry = *slot; + if (entry == NULL) + { + /* Only cache up to constexpr_cache_depth to limit memory use. */ + if (depth_ok < constexpr_cache_depth) + { + /* We need to keep a pointer to the entry, not just the slot, as + the slot can move during evaluation of the body. */ + *slot = entry = ggc_alloc<constexpr_call> (); + *entry = new_call; + fb.preserve (); + } + } + /* Calls that are in progress have their result set to NULL, so that we + can detect circular dependencies. Now that we only cache up to + constexpr_cache_depth this won't catch circular dependencies that + start deeper, but they'll hit the recursion or ops limit. */ + else if (entry->result == NULL) + { + if (!ctx->quiet) + error ("call has circular dependency"); + *non_constant_p = true; + entry->result = result = error_mark_node; + } + else + result = entry->result; + } + + if (!depth_ok) + { + if (!ctx->quiet) + error ("%<constexpr%> evaluation depth exceeds maximum of %d (use " + "%<-fconstexpr-depth=%> to increase the maximum)", + max_constexpr_depth); + *non_constant_p = true; + result = error_mark_node; + } + else + { + bool cacheable = true; + if (result && result != error_mark_node) + /* OK */; + else if (!DECL_SAVED_TREE (fun)) + { + /* When at_eof >= 2, cgraph has started throwing away + DECL_SAVED_TREE, so fail quietly. FIXME we get here because of + late code generation for VEC_INIT_EXPR, which needs to be + completely reconsidered. */ + // gcc_assert (at_eof >= 2 && ctx->quiet); + *non_constant_p = true; + } + else if (tree copy = get_fundef_copy (new_call.fundef)) + { + tree body, parms, res; + releasing_vec ctors; + + /* Reuse or create a new unshared copy of this function's body. */ + body = TREE_PURPOSE (copy); + parms = TREE_VALUE (copy); + res = TREE_TYPE (copy); + + /* Associate the bindings with the remapped parms. */ + tree bound = new_call.bindings; + tree remapped = parms; + for (int i = 0; i < TREE_VEC_LENGTH (bound); ++i) + { + tree arg = TREE_VEC_ELT (bound, i); + if (entry) + { + /* Unshare args going into the hash table to separate them + from the caller's context, for better GC and to avoid + problems with verify_gimple. */ + arg = unshare_expr_without_location (arg); + TREE_VEC_ELT (bound, i) = arg; + + /* And then unshare again so the callee doesn't change the + argument values in the hash table. XXX Could we unshare + lazily in cxx_eval_store_expression? */ + arg = unshare_constructor (arg); + if (TREE_CODE (arg) == CONSTRUCTOR) + vec_safe_push (ctors, arg); + } + + ctx->global->values.put (remapped, arg); + remapped = DECL_CHAIN (remapped); + } + /* Add the RESULT_DECL to the values map, too. */ + gcc_assert (!DECL_BY_REFERENCE (res)); + ctx->global->values.put (res, NULL_TREE); + + /* Track the callee's evaluated SAVE_EXPRs and TARGET_EXPRs so that + we can forget their values after the call. */ + constexpr_ctx ctx_with_save_exprs = *ctx; + auto_vec<tree, 10> save_exprs; + ctx_with_save_exprs.save_exprs = &save_exprs; + ctx_with_save_exprs.call = &new_call; + unsigned save_heap_alloc_count = ctx->global->heap_vars.length (); + unsigned save_heap_dealloc_count = ctx->global->heap_dealloc_count; + + tree jump_target = NULL_TREE; + constexpr_expression (&ctx_with_save_exprs, body, lval, + non_constant_p, overflow_p, &jump_target); + + if (VOID_TYPE_P (TREE_TYPE (res))) + result = void_node; + else + { + result = *ctx->global->values.get (res); + if (result == NULL_TREE && !*non_constant_p) + { + if (!ctx->quiet) + error ("%<constexpr%> call flows off the end " + "of the function"); + *non_constant_p = true; + } + } + + /* Forget the saved values of the callee's SAVE_EXPRs and + TARGET_EXPRs. */ + for (tree save_expr : save_exprs) + ctx->global->values.remove (save_expr); + + /* Remove the parms/result from the values map. Is it worth + bothering to do this when the map itself is only live for + one constexpr evaluation? If so, maybe also clear out + other vars from call, maybe in BIND_EXPR handling? */ + ctx->global->values.remove (res); + for (tree parm = parms; parm; parm = TREE_CHAIN (parm)) + ctx->global->values.remove (parm); + + /* Make the unshared function copy we used available for re-use. */ + save_fundef_copy (fun, copy); + + /* If the call allocated some heap object that hasn't been + deallocated during the call, or if it deallocated some heap + object it has not allocated, the call isn't really stateless + for the constexpr evaluation and should not be cached. + It is fine if the call allocates something and deallocates it + too. */ + if (entry + && (save_heap_alloc_count != ctx->global->heap_vars.length () + || (save_heap_dealloc_count + != ctx->global->heap_dealloc_count))) + { + tree heap_var; + unsigned int i; + if ((ctx->global->heap_vars.length () + - ctx->global->heap_dealloc_count) + != save_heap_alloc_count - save_heap_dealloc_count) + cacheable = false; + else + FOR_EACH_VEC_ELT_FROM (ctx->global->heap_vars, i, heap_var, + save_heap_alloc_count) + if (DECL_NAME (heap_var) != heap_deleted_identifier) + { + cacheable = false; + break; + } + } + } + else + /* Couldn't get a function copy to evaluate. */ + *non_constant_p = true; + + if (result == error_mark_node) + *non_constant_p = true; + if (*non_constant_p || *overflow_p) + result = error_mark_node; + else if (!result) + result = void_node; + if (entry) + entry->result = cacheable ? result : error_mark_node; + } + + pop_cx_call_context (); + return result; } // Subroutine of check_constexpr_fundef. BODY is the body of a function @@ -828,7 +1978,7 @@ unshare_constructor (tree t MEM_STAT_DECL) } /* Returns the index of the constructor_elt of ARY which matches DINDEX, or -1 - if none. If INSERT is true, insert a matching element rather than fail. */ + if none. If INSERT is true, insert a matching element rather than fail. */ static HOST_WIDE_INT find_array_ctor_elt (tree ary, tree dindex, bool insert) @@ -1269,5 +2419,168 @@ free_constructor (tree t) } } +/* Check whether the parameter and return types of FUN are valid for a + constexpr function, and complain if COMPLAIN. */ + +bool +is_valid_constexpr_fn (tree fun, bool complain) +{ + bool ret = true; + + for (tree parm = FUNCTION_FIRST_USER_PARM (fun); parm != NULL_TREE; + parm = TREE_CHAIN (parm)) + if (!literal_type_p (TREE_TYPE (parm))) + { + ret = false; + if (complain) + { + auto_diagnostic_group d; + error ("invalid type for parameter %d of %<constexpr%> " + "function %q+#D", + DECL_PARM_INDEX (parm), fun); + } + } + + return ret; +} + +void +explain_invalid_constexpr_fn (tree fun) +{ + static hash_set<tree> *diagnosed; + tree body; + + if (diagnosed == NULL) + diagnosed = new hash_set<tree>; + if (diagnosed->add (fun)) + /* Already explained. */ + return; + + iloc_sentinel ils = input_location; + // if (!lambda_static_thunk_p (fun)) + // { + // /* Diagnostics should completely ignore the static thunk, so leave + // input_location set to our caller's location. */ + // input_location = DECL_SOURCE_LOCATION (fun); + // inform (input_location, + // "%qD is not usable as a %<constexpr%> function because:", + // fun); + // } + + /* First check the declaration. */ + if (is_valid_constexpr_fn (fun, true)) + { + // /* Then if it's OK, the body. */ + // if (!DECL_DECLARED_CONSTEXPR_P (fun)) + // explain_implicit_non_constexpr (fun); + // else + // { + // if (constexpr_fundef *fd = retrieve_constexpr_fundef (fun)) + // body = fd->body; + // else + // body = DECL_SAVED_TREE (fun); + // body = massage_constexpr_body (fun, body); + // require_potential_rvalue_constant_expression (body); + // } + } +} + +/* BODY is a validated and massaged definition of a constexpr + function. Register it in the hash table. */ + +void +register_constexpr_fundef (const constexpr_fundef &value) +{ + /* Create the constexpr function table if necessary. */ + if (constexpr_fundef_table == NULL) + constexpr_fundef_table + = hash_table<constexpr_fundef_hasher>::create_ggc (101); + + constexpr_fundef **slot = constexpr_fundef_table->find_slot ( + const_cast<constexpr_fundef *> (&value), INSERT); + + gcc_assert (*slot == NULL); + *slot = ggc_alloc<constexpr_fundef> (); + **slot = value; +} + +/* We are processing the definition of the constexpr function FUN. + Check that its body fulfills the apropriate requirements and + enter it in the constexpr function definition table. */ + +void +maybe_save_constexpr_fundef (tree fun) +{ + // FIXME + + constexpr_fundef entry = {fun, NULL_TREE, NULL_TREE, NULL_TREE}; + bool clear_ctx = false; + if (DECL_RESULT (fun) && DECL_CONTEXT (DECL_RESULT (fun)) == NULL_TREE) + { + clear_ctx = true; + DECL_CONTEXT (DECL_RESULT (fun)) = fun; + } + tree saved_fn = current_function_decl; + current_function_decl = fun; + entry.body = copy_fn (entry.decl, entry.parms, entry.result); + current_function_decl = saved_fn; + if (clear_ctx) + DECL_CONTEXT (DECL_RESULT (entry.decl)) = NULL_TREE; + + register_constexpr_fundef (entry); +} + +/* Evaluate a STATEMENT_LIST for side-effects. Handles various jump + semantics, for switch, break, continue, and return. */ + +static tree +eval_statement_list (const constexpr_ctx *ctx, tree t, bool *non_constant_p, + bool *overflow_p, tree *jump_target) +{ + tree local_target; + /* In a statement-expression we want to return the last value. + For empty statement expression return void_node. */ + tree r = void_node; + if (!jump_target) + { + local_target = NULL_TREE; + jump_target = &local_target; + } + for (tree stmt : tsi_range (t)) + { + /* We've found a continue, so skip everything until we reach + the label its jumping to. */ + // FIXME + // if (continues (jump_target)) + // { + // if (label_matches (ctx, jump_target, stmt)) + // /* Found it. */ + // *jump_target = NULL_TREE; + // else + // continue; + // } + if (TREE_CODE (stmt) == DEBUG_BEGIN_STMT) + continue; + r = constexpr_expression (ctx, stmt, false, non_constant_p, overflow_p, + jump_target); + if (*non_constant_p) + break; + // FIXME + // if (returns (jump_target) || breaks (jump_target)) + // break; + } + if (*jump_target && jump_target == &local_target) + { + /* We aren't communicating the jump to our caller, so give up. We don't + need to support evaluation of jumps out of statement-exprs. */ + if (!ctx->quiet) + error_at (EXPR_LOCATION (r), "statement is not a constant expression"); + *non_constant_p = true; + } + return r; +} + +// #include "gt-rust-rust-constexpr.h" + } // namespace Compile } // namespace Rust |