// This file is part of GCC. // GCC is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3, or (at your option) any later // version. // GCC is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // You should have received a copy of the GNU General Public License // along with GCC; see the file COPYING3. If not see // . #include "rust-constexpr.h" #include "rust-location.h" #include "rust-diagnostics.h" #include "rust-tree.h" #include "fold-const.h" #include "realmpfr.h" #include "convert.h" #include "print-tree.h" #include "gimplify.h" #include "tree-iterator.h" #include "timevar.h" #include "varasm.h" #include "cgraph.h" #include "tree-inline.h" #include "vec.h" #define VERIFY_CONSTANT(X) \ do \ { \ if (verify_constant ((X), ctx->quiet, non_constant_p, overflow_p)) \ return t; \ } \ while (0) 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 *); static HOST_WIDE_INT find_array_ctor_elt (tree ary, tree dindex, bool insert = false); 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 { /* Values for any temporaries or local variables within the constant-expression. */ hash_map values; /* Number of cxx_eval_constant_expression calls (except skipped ones, on simple constants or location wrappers) encountered during current cxx_eval_outermost_constant_expr call. */ HOST_WIDE_INT constexpr_ops_count; /* Heap VAR_DECLs created during the evaluation of the outermost constant expression. */ auto_vec heap_vars; /* Cleanups that need to be evaluated at the end of CLEANUP_POINT_EXPR. */ vec *cleanups; /* Number of heap VAR_DECL deallocations. */ unsigned heap_dealloc_count; /* Constructor. */ constexpr_global_ctx () : constexpr_ops_count (0), cleanups (NULL), heap_dealloc_count (0) {} }; /* In constexpr.cc */ /* Representation of entries in the constexpr function definition table. */ struct GTY ((for_user)) constexpr_fundef { tree decl; tree body; tree parms; tree result; }; /* Objects of this type represent calls to constexpr functions along with the bindings of parameters to their arguments, for the purpose of compile time evaluation. */ struct GTY ((for_user)) constexpr_call { /* Description of the constexpr function definition. */ constexpr_fundef *fundef; /* Parameter bindings environment. A TREE_VEC of arguments. */ tree bindings; /* Result of the call. NULL means the call is being evaluated. error_mark_node means that the evaluation was erroneous; otherwise, the actuall value of the call. */ tree result; /* The hash of this call; we remember it here to avoid having to recalculate it when expanding the hash table. */ hashval_t hash; /* Whether __builtin_is_constant_evaluated() should evaluate to true. */ bool manifestly_const_eval; }; struct constexpr_call_hasher : ggc_ptr_hash { static hashval_t hash (constexpr_call *); static bool equal (constexpr_call *, constexpr_call *); }; enum constexpr_switch_state { /* Used when processing a switch for the first time by cxx_eval_switch_expr and default: label for that switch has not been seen yet. */ css_default_not_seen, /* Used when processing a switch for the first time by cxx_eval_switch_expr and default: label for that switch has been seen already. */ css_default_seen, /* Used when processing a switch for the second time by cxx_eval_switch_expr, where default: label should match. */ css_default_processing }; struct constexpr_ctx { /* The part of the context that needs to be unique to the whole cxx_eval_outermost_constant_expr invocation. */ constexpr_global_ctx *global; /* The innermost call we're evaluating. */ constexpr_call *call; /* SAVE_EXPRs and TARGET_EXPR_SLOT vars of TARGET_EXPRs that we've seen within the current LOOP_EXPR. NULL if we aren't inside a loop. */ vec *save_exprs; /* The CONSTRUCTOR we're currently building up for an aggregate initializer. */ tree ctor; /* The object we're building the CONSTRUCTOR for. */ tree object; /* If inside SWITCH_EXPR. */ constexpr_switch_state *css_state; /* The aggregate initialization context inside which this one is nested. This is used by lookup_placeholder to resolve PLACEHOLDER_EXPRs. */ const constexpr_ctx *parent; /* Whether we should error on a non-constant expression or fail quietly. This flag needs to be here, but some of the others could move to global if they get larger than a word. */ bool quiet; /* Whether we are strictly conforming to constant expression rules or trying harder to get a constant value. */ bool strict; /* Whether __builtin_is_constant_evaluated () should be true. */ bool manifestly_const_eval; }; struct constexpr_fundef_hasher : ggc_ptr_hash { 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_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 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 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_table; static tree constexp_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::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 (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); tree decl_constant_value (tree decl, bool unshare_p); static void non_const_var_error (location_t loc, tree r); static tree eval_constant_expression (const constexpr_ctx *ctx, tree, bool, bool *, bool *, tree * = NULL); static tree constexpr_fn_retval (const constexpr_ctx *ctx, tree r); 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, bool, bool *, bool *); static tree 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); static tree eval_conditional_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p, tree *jump_target); /* 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 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 cx_error_context (void) { vec 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) { bool allow_non_constant = false; bool strict = true; bool manifestly_const_eval = false; constexpr_global_ctx global_ctx; constexpr_ctx ctx = {&global_ctx, NULL, NULL, NULL, NULL, NULL, NULL, allow_non_constant, strict, manifestly_const_eval || !allow_non_constant}; auto_vec cleanups; global_ctx.cleanups = &cleanups; bool non_constant_p = false; bool overflow_p = false; tree folded = eval_constant_expression (&ctx, expr, false, &non_constant_p, &overflow_p); rust_assert (folded != NULL_TREE); // more logic here to possibly port return folded; } static tree eval_constant_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p, tree *jump_target /* = NULL */) { location_t loc = EXPR_LOCATION (t); if (CONSTANT_CLASS_P (t)) { if (TREE_OVERFLOW (t)) { error_at (loc, "overflow in constant expression"); return t; } return t; } // Avoid excessively long constexpr evaluations if (++ctx->global->constexpr_ops_count >= constexpr_ops_limit) { rust_error_at ( Location (loc), "% evaluation operation count exceeds limit of " "%wd (use %<-fconstexpr-ops-limit=%> to increase the limit)", constexpr_ops_limit); return t; } tree r = t; tree_code tcode = TREE_CODE (t); switch (tcode) { case CONST_DECL: { r = decl_constant_value (t, /*unshare_p=*/false); if (TREE_CODE (r) == TARGET_EXPR && TREE_CODE (TARGET_EXPR_INITIAL (r)) == CONSTRUCTOR) r = TARGET_EXPR_INITIAL (r); if (DECL_P (r)) { non_const_var_error (loc, r); return r; } } 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: case MINUS_EXPR: case MULT_EXPR: case TRUNC_DIV_EXPR: case CEIL_DIV_EXPR: case FLOOR_DIV_EXPR: case ROUND_DIV_EXPR: case TRUNC_MOD_EXPR: case CEIL_MOD_EXPR: case ROUND_MOD_EXPR: case RDIV_EXPR: case EXACT_DIV_EXPR: case MIN_EXPR: case MAX_EXPR: case LSHIFT_EXPR: case RSHIFT_EXPR: case LROTATE_EXPR: case RROTATE_EXPR: case BIT_IOR_EXPR: case BIT_XOR_EXPR: case BIT_AND_EXPR: case TRUTH_XOR_EXPR: case LT_EXPR: case LE_EXPR: case GT_EXPR: case GE_EXPR: case EQ_EXPR: case NE_EXPR: case SPACESHIP_EXPR: case UNORDERED_EXPR: case ORDERED_EXPR: case UNLT_EXPR: case UNLE_EXPR: case UNGT_EXPR: case UNGE_EXPR: case UNEQ_EXPR: case LTGT_EXPR: case RANGE_EXPR: case COMPLEX_EXPR: r = eval_binary_expression (ctx, t, false, non_constant_p, overflow_p); break; case CALL_EXPR: r = eval_call_expression (ctx, t, false, non_constant_p, overflow_p); break; case RETURN_EXPR: rust_assert (TREE_OPERAND (t, 0) != NULL_TREE); r = eval_constant_expression (ctx, TREE_OPERAND (t, 0), false, non_constant_p, overflow_p); break; case MODIFY_EXPR: 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 eval_constant_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 = eval_constant_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; case COND_EXPR: case IF_STMT: if (jump_target && *jump_target) { tree orig_jump = *jump_target; tree arg = ((TREE_CODE (t) != IF_STMT || TREE_OPERAND (t, 1)) ? TREE_OPERAND (t, 1) : void_node); /* When jumping to a label, the label might be either in the then or else blocks, so process then block first in skipping mode first, and if we are still in the skipping mode at its end, process the else block too. */ r = eval_constant_expression (ctx, arg, lval, non_constant_p, overflow_p, jump_target); /* It's possible that we found the label in the then block. But it could have been followed by another jumping statement, e.g. say we're looking for case 1: if (cond) { // skipped statements case 1:; // clears up *jump_target return 1; // and sets it to a RETURN_EXPR } else { ... } in which case we need not go looking to the else block. (goto is not allowed in a constexpr function.) */ if (*jump_target == orig_jump) { arg = ((TREE_CODE (t) != IF_STMT || TREE_OPERAND (t, 2)) ? TREE_OPERAND (t, 2) : void_node); r = eval_constant_expression (ctx, arg, lval, non_constant_p, overflow_p, jump_target); } break; } r = eval_conditional_expression (ctx, t, lval, non_constant_p, overflow_p, jump_target); break; default: break; } 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 *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 % 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. */ return void_node; /* First we figure out where we're storing to. */ tree target = TREE_OPERAND (t, 0); tree type = TREE_TYPE (target); bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR; if (preeval) { /* Evaluate the value to be stored without knowing what object it will be stored in, so that any side-effects happen first. */ if (!SCALAR_TYPE_P (type)) new_ctx.ctor = new_ctx.object = NULL_TREE; init = eval_constant_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 = eval_constant_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 = eval_constant_expression (ctx, probe, true, non_constant_p, overflow_p); evaluated = true; if (*non_constant_p) return t; } break; } } // 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 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 = eval_constant_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. Like cxx_eval_unary_expression, except for binary expressions. */ static tree eval_binary_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p) { tree orig_lhs = TREE_OPERAND (t, 0); tree orig_rhs = TREE_OPERAND (t, 1); tree lhs, rhs; lhs = eval_constant_expression (ctx, orig_lhs, lval, non_constant_p, overflow_p); rhs = eval_constant_expression (ctx, orig_rhs, lval, non_constant_p, overflow_p); location_t loc = EXPR_LOCATION (t); enum tree_code code = TREE_CODE (t); tree type = TREE_TYPE (t); return fold_binary_loc (loc, code, type, lhs, rhs); } /* TEMP is the constant value of a temporary object of type TYPE. Adjust the type of the value to match. */ static tree adjust_temp_type (tree type, tree temp) { if (same_type_p (TREE_TYPE (temp), type)) return temp; gcc_assert (scalarish_type_p (type)); /* Now we know we're dealing with a scalar, and a prvalue of non-class type is cv-unqualified. */ return fold_convert (cv_unqualified (type), temp); } /* Helper function of cxx_bind_parameters_in_call. Return non-NULL if *TP is address of a static variable (or part of it) currently being constructed or of a heap artificial variable. */ static tree addr_of_non_const_var (tree *tp, int *walk_subtrees, void *data) { if (TREE_CODE (*tp) == ADDR_EXPR) if (tree var = get_base_address (TREE_OPERAND (*tp, 0))) if (VAR_P (var) && TREE_STATIC (var)) { if (DECL_NAME (var) == heap_uninit_identifier || DECL_NAME (var) == heap_identifier || DECL_NAME (var) == heap_vec_uninit_identifier || DECL_NAME (var) == heap_vec_identifier) return var; constexpr_global_ctx *global = (constexpr_global_ctx *) data; if (global->values.get (var)) return var; } if (TYPE_P (*tp)) *walk_subtrees = false; return NULL_TREE; } /* Subroutine of cxx_eval_call_expression. We are processing a call expression (either CALL_EXPR or AGGR_INIT_EXPR) in the context of CTX. Evaluate all arguments and bind their values to correspondings parameters, making up the NEW_CALL context. */ static tree rs_bind_parameters_in_call (const constexpr_ctx *ctx, tree t, tree fun, bool *non_constant_p, bool *overflow_p, bool *non_constant_args) { const int nargs = call_expr_nargs (t); tree parms = DECL_ARGUMENTS (fun); int i; /* We don't record ellipsis args below. */ int nparms = list_length (parms); int nbinds = nargs < nparms ? nargs : nparms; tree binds = make_tree_vec (nbinds); for (i = 0; i < nargs; ++i) { tree x, arg; tree type = parms ? TREE_TYPE (parms) : void_type_node; if (parms && DECL_BY_REFERENCE (parms)) type = TREE_TYPE (type); x = get_nth_callarg (t, i); if (TREE_ADDRESSABLE (type)) /* Undo convert_for_arg_passing work here. */ x = convert_from_reference (x); /* Normally we would strip a TARGET_EXPR in an initialization context such as this, but here we do the elision differently: we keep the TARGET_EXPR, and use its CONSTRUCTOR as the value of the parm. */ arg = eval_constant_expression (ctx, x, /*lval=*/false, non_constant_p, overflow_p); /* Don't VERIFY_CONSTANT here. */ if (*non_constant_p && ctx->quiet) break; /* Just discard ellipsis args after checking their constantitude. */ if (!parms) continue; if (!*non_constant_p) { /* Make sure the binding has the same type as the parm. But only for constant args. */ if (!TYPE_REF_P (type)) arg = adjust_temp_type (type, arg); if (!TREE_CONSTANT (arg)) *non_constant_args = true; else if (TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type)) /* The destructor needs to see any modifications the callee makes to the argument. */ *non_constant_args = true; /* If arg is or contains address of a heap artificial variable or of a static variable being constructed, avoid caching the function call, as those variables might be modified by the function, or might be modified by the callers in between the cached function and just read by the function. */ else if (!*non_constant_args && rs_walk_tree (&arg, addr_of_non_const_var, ctx->global, NULL)) *non_constant_args = true; // /* For virtual calls, adjust the this argument, so that it is // the object on which the method is called, rather than // one of its bases. */ // if (i == 0 && DECL_VIRTUAL_P (fun)) // { // tree addr = arg; // STRIP_NOPS (addr); // if (TREE_CODE (addr) == ADDR_EXPR) // { // tree obj = TREE_OPERAND (addr, 0); // while (TREE_CODE (obj) == COMPONENT_REF // && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1)) // && !same_type_ignoring_top_level_qualifiers_p ( // TREE_TYPE (obj), DECL_CONTEXT (fun))) // obj = TREE_OPERAND (obj, 0); // if (obj != TREE_OPERAND (addr, 0)) // arg = build_fold_addr_expr_with_type (obj, TREE_TYPE // (arg)); // } // } TREE_VEC_ELT (binds, i) = arg; } parms = TREE_CHAIN (parms); } return binds; } // Subroutine of cxx_eval_constant_expression. // 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, bool lval, bool *non_constant_p, bool *overflow_p) { location_t loc = EXPR_LOCATION (t); tree fun = get_function_named_in_call (t); 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 % " "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 (); *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 ("% 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 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; eval_constant_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 ("% 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 // declared to be constexpr, or a sub-statement thereof. Returns the // return value if suitable, error_mark_node for a statement not allowed in // a constexpr function, or NULL_TREE if no return value was found. static tree constexpr_fn_retval (const constexpr_ctx *ctx, tree body) { switch (TREE_CODE (body)) { case STATEMENT_LIST: { tree expr = NULL_TREE; for (tree stmt : tsi_range (body)) { tree s = constexpr_fn_retval (ctx, stmt); if (s == error_mark_node) return error_mark_node; else if (s == NULL_TREE) /* Keep iterating. */; else if (expr) /* Multiple return statements. */ return error_mark_node; else expr = s; } return expr; } case RETURN_EXPR: { bool non_constant_p = false; bool overflow_p = false; return eval_constant_expression (ctx, body, false, &non_constant_p, &overflow_p); } case DECL_EXPR: { tree decl = DECL_EXPR_DECL (body); if (TREE_CODE (decl) == USING_DECL /* Accept __func__, __FUNCTION__, and __PRETTY_FUNCTION__. */ || DECL_ARTIFICIAL (decl)) return NULL_TREE; return error_mark_node; } case CLEANUP_POINT_EXPR: return constexpr_fn_retval (ctx, TREE_OPERAND (body, 0)); case BIND_EXPR: { tree b = BIND_EXPR_BODY (body); return constexpr_fn_retval (ctx, b); } break; default: return error_mark_node; } return error_mark_node; } // Taken from cp/constexpr.cc // // If DECL is a scalar enumeration constant or variable with a // constant initializer, return the initializer (or, its initializers, // recursively); otherwise, return DECL. If STRICT_P, the // initializer is only returned if DECL is a // constant-expression. If RETURN_AGGREGATE_CST_OK_P, it is ok to // return an aggregate constant. If UNSHARE_P, return an unshared // copy of the initializer. static tree constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p, bool unshare_p) { while (TREE_CODE (decl) == CONST_DECL) { tree init; /* If DECL is a static data member in a template specialization, we must instantiate it here. The initializer for the static data member is not processed until needed; we need it now. */ init = DECL_INITIAL (decl); if (init == error_mark_node) { if (TREE_CODE (decl) == CONST_DECL) /* Treat the error as a constant to avoid cascading errors on excessively recursive template instantiation (c++/9335). */ return init; else return decl; } decl = init; } return unshare_p ? unshare_expr (decl) : decl; } // A more relaxed version of decl_really_constant_value, used by the // common C/C++ code. tree decl_constant_value (tree decl, bool unshare_p) { return constant_value_1 (decl, /*strict_p=*/false, /*return_aggregate_cst_ok_p=*/true, /*unshare_p=*/unshare_p); } static void non_const_var_error (location_t loc, tree r) { error_at (loc, "the value of %qD is not usable in a constant " "expression", r); /* Avoid error cascade. */ if (DECL_INITIAL (r) == error_mark_node) return; // more in cp/constexpr.cc } static tree get_callee (tree call) { if (call == NULL_TREE) return call; else if (TREE_CODE (call) == CALL_EXPR) return CALL_EXPR_FN (call); return NULL_TREE; } // We have an expression tree T that represents a call, either CALL_EXPR // or AGGR_INIT_EXPR. If the call is lexically to a named function, // return the _DECL for that function. static tree get_function_named_in_call (tree t) { tree fun = get_callee (t); if (fun && TREE_CODE (fun) == ADDR_EXPR && TREE_CODE (TREE_OPERAND (fun, 0)) == FUNCTION_DECL) fun = TREE_OPERAND (fun, 0); return fun; } // forked from gcc/cp/constexpr.cc maybe_constexpr_fn /* True if a function might be declared constexpr */ bool maybe_constexpr_fn (tree t) { return (DECL_DECLARED_CONSTEXPR_P (t)); } // forked from gcc/cp/constexpr.cc get_nth_callarg /* We have an expression tree T that represents a call, either CALL_EXPR. Return the Nth argument. */ inline tree get_nth_callarg (tree t, int n) { switch (TREE_CODE (t)) { case CALL_EXPR: return CALL_EXPR_ARG (t, n); default: gcc_unreachable (); return NULL; } } // forked from gcc/cp/constexpr.cc var_in_maybe_constexpr_fn /* True if T was declared in a function that might be constexpr: either a function that was declared constexpr. */ bool var_in_maybe_constexpr_fn (tree t) { return (DECL_FUNCTION_SCOPE_P (t) && maybe_constexpr_fn (DECL_CONTEXT (t))); } // forked from gcc/cp/constexpr.cc array_index_cmp /* Some of the expressions fed to the constexpr mechanism are calls to constructors, which have type void. In that case, return the type being initialized by the constructor. */ static tree initialized_type (tree t) { if (TYPE_P (t)) return t; tree type = TREE_TYPE (t); if (TREE_CODE (t) == CALL_EXPR) { /* A constructor call has void type, so we need to look deeper. */ tree fn = get_function_named_in_call (t); if (fn && TREE_CODE (fn) == FUNCTION_DECL && DECL_CXX_CONSTRUCTOR_P (fn)) type = DECL_CONTEXT (fn); } else if (TREE_CODE (t) == COMPOUND_EXPR) return initialized_type (TREE_OPERAND (t, 1)); return cv_unqualified (type); } /* P0859: A function is needed for constant evaluation if it is a constexpr function that is named by an expression ([basic.def.odr]) that is potentially constant evaluated. So we need to instantiate any constexpr functions mentioned by the expression even if the definition isn't needed for evaluating the expression. */ static tree instantiate_cx_fn_r (tree *tp, int *walk_subtrees, void * /*data*/) { if (TREE_CODE (*tp) == CALL_EXPR) { if (EXPR_HAS_LOCATION (*tp)) input_location = EXPR_LOCATION (*tp); } if (!EXPR_P (*tp)) *walk_subtrees = 0; return NULL_TREE; } static void instantiate_constexpr_fns (tree t) { location_t loc = input_location; rs_walk_tree_without_duplicates (&t, instantiate_cx_fn_r, NULL); input_location = loc; } /* Returns less than, equal to, or greater than zero if KEY is found to be less than, to match, or to be greater than the constructor_elt's INDEX. */ static int array_index_cmp (tree key, tree index) { gcc_assert (TREE_CODE (key) == INTEGER_CST); switch (TREE_CODE (index)) { case INTEGER_CST: return tree_int_cst_compare (key, index); case RANGE_EXPR: { tree lo = TREE_OPERAND (index, 0); tree hi = TREE_OPERAND (index, 1); if (tree_int_cst_lt (key, lo)) return -1; else if (tree_int_cst_lt (hi, key)) return 1; else return 0; } default: gcc_unreachable (); } } /* If T is a CONSTRUCTOR, return an unshared copy of T and any sub-CONSTRUCTORs. Otherwise return T. We use this whenever we initialize an object as a whole, whether it's a parameter, a local variable, or a subobject, so that subsequent modifications don't affect other places where it was used. */ tree unshare_constructor (tree t MEM_STAT_DECL) { if (!t || TREE_CODE (t) != CONSTRUCTOR) return t; auto_vec ptrs; ptrs.safe_push (&t); while (!ptrs.is_empty ()) { tree *p = ptrs.pop (); tree n = copy_node (*p PASS_MEM_STAT); CONSTRUCTOR_ELTS (n) = vec_safe_copy (CONSTRUCTOR_ELTS (*p) PASS_MEM_STAT); *p = n; vec *v = CONSTRUCTOR_ELTS (n); constructor_elt *ce; for (HOST_WIDE_INT i = 0; vec_safe_iterate (v, i, &ce); ++i) if (ce->value && TREE_CODE (ce->value) == CONSTRUCTOR) ptrs.safe_push (&ce->value); } return t; } /* 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. */ static HOST_WIDE_INT find_array_ctor_elt (tree ary, tree dindex, bool insert) { if (tree_int_cst_sgn (dindex) < 0) return -1; unsigned HOST_WIDE_INT i = tree_to_uhwi (dindex); vec *elts = CONSTRUCTOR_ELTS (ary); unsigned HOST_WIDE_INT len = vec_safe_length (elts); unsigned HOST_WIDE_INT end = len; unsigned HOST_WIDE_INT begin = 0; /* If the last element of the CONSTRUCTOR has its own index, we can assume that the same is true of the other elements and index directly. */ if (end > 0) { tree cindex = (*elts)[end - 1].index; if (cindex == NULL_TREE) { /* Verify that if the last index is missing, all indexes are missing. */ if (flag_checking) for (unsigned int j = 0; j < len - 1; ++j) gcc_assert ((*elts)[j].index == NULL_TREE); if (i < end) return i; else { begin = end; if (i == end) /* If the element is to be added right at the end, make sure it is added with cleared index too. */ dindex = NULL_TREE; else if (insert) /* Otherwise, in order not to break the assumption that CONSTRUCTOR either has all indexes or none, we need to add indexes to all elements. */ for (unsigned int j = 0; j < len; ++j) (*elts)[j].index = build_int_cst (TREE_TYPE (dindex), j); } } else if (TREE_CODE (cindex) == INTEGER_CST && compare_tree_int (cindex, end - 1) == 0) { if (i < end) return i; else begin = end; } } /* Otherwise, find a matching index by means of a binary search. */ while (begin != end) { unsigned HOST_WIDE_INT middle = (begin + end) / 2; constructor_elt &elt = (*elts)[middle]; tree idx = elt.index; int cmp = array_index_cmp (dindex, idx); if (cmp < 0) end = middle; else if (cmp > 0) begin = middle + 1; else { if (insert && TREE_CODE (idx) == RANGE_EXPR) { /* We need to split the range. */ constructor_elt e; tree lo = TREE_OPERAND (idx, 0); tree hi = TREE_OPERAND (idx, 1); tree value = elt.value; dindex = fold_convert (sizetype, dindex); if (tree_int_cst_lt (lo, dindex)) { /* There are still some lower elts; shorten the range. */ tree new_hi = int_const_binop (MINUS_EXPR, dindex, size_one_node); if (tree_int_cst_equal (lo, new_hi)) /* Only one element left, no longer a range. */ elt.index = lo; else TREE_OPERAND (idx, 1) = new_hi; /* Append the element we want to insert. */ ++middle; e.index = dindex; e.value = unshare_constructor (value); vec_safe_insert (CONSTRUCTOR_ELTS (ary), middle, e); } else /* No lower elts, the range elt is now ours. */ elt.index = dindex; if (tree_int_cst_lt (dindex, hi)) { /* There are still some higher elts; append a range. */ tree new_lo = int_const_binop (PLUS_EXPR, dindex, size_one_node); if (tree_int_cst_equal (new_lo, hi)) e.index = hi; else e.index = build2 (RANGE_EXPR, sizetype, new_lo, hi); e.value = unshare_constructor (value); vec_safe_insert (CONSTRUCTOR_ELTS (ary), middle + 1, e); } } return middle; } } if (insert) { constructor_elt e = {dindex, NULL_TREE}; vec_safe_insert (CONSTRUCTOR_ELTS (ary), end, e); return end; } return -1; } /* Return true if T is a valid constant initializer. If a CONSTRUCTOR initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be cleared. FIXME speed this up, it's taking 16% of compile time on sieve testcase. */ bool reduced_constant_expression_p (tree t) { if (t == NULL_TREE) return false; switch (TREE_CODE (t)) { case PTRMEM_CST: /* Even if we can't lower this yet, it's constant. */ return true; case CONSTRUCTOR: /* And we need to handle PTRMEM_CST wrapped in a CONSTRUCTOR. */ tree field; if (CONSTRUCTOR_NO_CLEARING (t)) { if (TREE_CODE (TREE_TYPE (t)) == VECTOR_TYPE) /* An initialized vector would have a VECTOR_CST. */ return false; else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE) { /* There must be a valid constant initializer at every array index. */ tree min = TYPE_MIN_VALUE (TYPE_DOMAIN (TREE_TYPE (t))); tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (TREE_TYPE (t))); tree cursor = min; for (auto &e : CONSTRUCTOR_ELTS (t)) { if (!reduced_constant_expression_p (e.value)) return false; if (array_index_cmp (cursor, e.index) != 0) return false; if (TREE_CODE (e.index) == RANGE_EXPR) cursor = TREE_OPERAND (e.index, 1); cursor = int_const_binop (PLUS_EXPR, cursor, size_one_node); } if (find_array_ctor_elt (t, max) == -1) return false; goto ok; } else if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE) { if (CONSTRUCTOR_NELTS (t) == 0) /* An initialized union has a constructor element. */ return false; /* And it only initializes one member. */ field = NULL_TREE; } else field = next_initializable_field (TYPE_FIELDS (TREE_TYPE (t))); } else field = NULL_TREE; for (auto &e : CONSTRUCTOR_ELTS (t)) { /* If VAL is null, we're in the middle of initializing this element. */ if (!reduced_constant_expression_p (e.value)) return false; /* Empty class field may or may not have an initializer. */ for (; field && e.index != field; field = next_initializable_field (DECL_CHAIN (field))) if (!is_really_empty_class (TREE_TYPE (field), /*ignore_vptr*/ false)) return false; if (field) field = next_initializable_field (DECL_CHAIN (field)); } /* There could be a non-empty field at the end. */ for (; field; field = next_initializable_field (DECL_CHAIN (field))) if (!is_really_empty_class (TREE_TYPE (field), /*ignore_vptr*/ false)) return false; ok: if (CONSTRUCTOR_NO_CLEARING (t)) /* All the fields are initialized. */ CONSTRUCTOR_NO_CLEARING (t) = false; return true; default: /* FIXME are we calling this too much? */ return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE; } } /* Some expressions may have constant operands but are not constant themselves, such as 1/0. Call this function to check for that condition. We only call this in places that require an arithmetic constant, not in places where we might have a non-constant expression that can be a component of a constant expression, such as the address of a constexpr variable that might be dereferenced later. */ static bool verify_constant (tree t, bool allow_non_constant, bool *non_constant_p, bool *overflow_p) { if (!*non_constant_p && !reduced_constant_expression_p (t) && t != void_node) { if (!allow_non_constant) error ("%q+E is not a constant expression", t); *non_constant_p = true; } if (TREE_OVERFLOW_P (t)) { if (!allow_non_constant) { permerror (input_location, "overflow in constant expression"); /* If we're being permissive (and are in an enforcing context), ignore the overflow. */ if (flag_permissive) return *non_constant_p; } *overflow_p = true; } return *non_constant_p; } // forked from gcc/cp/constexpr.cc find_heap_var_refs /* Look for heap variables in the expression *TP. */ static tree find_heap_var_refs (tree *tp, int *walk_subtrees, void * /*data*/) { if (VAR_P (*tp) && (DECL_NAME (*tp) == heap_uninit_identifier || DECL_NAME (*tp) == heap_identifier || DECL_NAME (*tp) == heap_vec_uninit_identifier || DECL_NAME (*tp) == heap_vec_identifier || DECL_NAME (*tp) == heap_deleted_identifier)) return *tp; if (TYPE_P (*tp)) *walk_subtrees = 0; return NULL_TREE; } // forked from gcc/cp/constexpr.cc find_immediate_fndecl /* Find immediate function decls in *TP if any. */ static tree find_immediate_fndecl (tree *tp, int * /*walk_subtrees*/, void * /*data*/) { if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp)) return *tp; if (TREE_CODE (*tp) == PTRMEM_CST && TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp))) return PTRMEM_CST_MEMBER (*tp); return NULL_TREE; } // forked in gcc/cp/constexpr.cc diag_array_subscript /* Under the control of CTX, issue a detailed diagnostic for an out-of-bounds subscript INDEX into the expression ARRAY. */ static void diag_array_subscript (location_t loc, const constexpr_ctx *ctx, tree array, tree index) { if (!ctx->quiet) { tree arraytype = TREE_TYPE (array); /* Convert the unsigned array subscript to a signed integer to avoid printing huge numbers for small negative values. */ tree sidx = fold_convert (ssizetype, index); STRIP_ANY_LOCATION_WRAPPER (array); if (DECL_P (array)) { if (TYPE_DOMAIN (arraytype)) error_at (loc, "array subscript value %qE is outside the bounds " "of array %qD of type %qT", sidx, array, arraytype); else error_at (loc, "nonzero array subscript %qE is used with array %qD of " "type %qT with unknown bounds", sidx, array, arraytype); inform (DECL_SOURCE_LOCATION (array), "declared here"); } else if (TYPE_DOMAIN (arraytype)) error_at (loc, "array subscript value %qE is outside the bounds " "of array type %qT", sidx, arraytype); else error_at (loc, "nonzero array subscript %qE is used with array of type %qT " "with unknown bounds", sidx, arraytype); } } // forked from gcc/cp/constexpr.cc get_array_or_vector_nelts /* Return the number of elements for TYPE (which is an ARRAY_TYPE or a VECTOR_TYPE). */ static tree get_array_or_vector_nelts (const constexpr_ctx *ctx, tree type, bool *non_constant_p, bool *overflow_p) { tree nelts; if (TREE_CODE (type) == ARRAY_TYPE) { if (TYPE_DOMAIN (type)) nelts = array_type_nelts_top (type); else nelts = size_zero_node; } else if (VECTOR_TYPE_P (type)) nelts = size_int (TYPE_VECTOR_SUBPARTS (type)); else gcc_unreachable (); /* For VLAs, the number of elements won't be an integer constant. */ nelts = eval_constant_expression (ctx, nelts, false, non_constant_p, overflow_p); return nelts; } // forked from gcc/cp/constexpr.cc eval_and_check_array_index /* Subroutine of cxx_eval_array_reference. T is an ARRAY_REF; evaluate the subscript, diagnose any problems with it, and return the result. */ static tree eval_and_check_array_index (const constexpr_ctx *ctx, tree t, bool allow_one_past, bool *non_constant_p, bool *overflow_p) { location_t loc = rs_expr_loc_or_input_loc (t); tree ary = TREE_OPERAND (t, 0); t = TREE_OPERAND (t, 1); tree index = eval_constant_expression (ctx, t, allow_one_past, non_constant_p, overflow_p); VERIFY_CONSTANT (index); if (!tree_fits_shwi_p (index) || tree_int_cst_sgn (index) < 0) { diag_array_subscript (loc, ctx, ary, index); *non_constant_p = true; return t; } tree nelts = get_array_or_vector_nelts (ctx, TREE_TYPE (ary), non_constant_p, overflow_p); VERIFY_CONSTANT (nelts); if (allow_one_past ? !tree_int_cst_le (index, nelts) : !tree_int_cst_lt (index, nelts)) { diag_array_subscript (loc, ctx, ary, index); *non_constant_p = true; return t; } return index; } // forked from gcc/cp/constexpr.cc extract_string_elt /* Extract element INDEX consisting of CHARS_PER_ELT chars from STRING_CST STRING. */ static tree extract_string_elt (tree string, unsigned chars_per_elt, unsigned index) { tree type = cv_unqualified (TREE_TYPE (TREE_TYPE (string))); tree r; if (chars_per_elt == 1) r = build_int_cst (type, TREE_STRING_POINTER (string)[index]); else { const unsigned char *ptr = ((const unsigned char *) TREE_STRING_POINTER (string) + index * chars_per_elt); r = native_interpret_expr (type, ptr, chars_per_elt); } return r; } // forked from gcc/cp/constexpr.cc free_constructor /* If T is a CONSTRUCTOR, ggc_free T and any sub-CONSTRUCTORs. */ static void free_constructor (tree t) { if (!t || TREE_CODE (t) != CONSTRUCTOR) return; releasing_vec ctors; vec_safe_push (ctors, t); while (!ctors->is_empty ()) { tree c = ctors->pop (); if (vec *elts = CONSTRUCTOR_ELTS (c)) { constructor_elt *ce; for (HOST_WIDE_INT i = 0; vec_safe_iterate (elts, i, &ce); ++i) if (TREE_CODE (ce->value) == CONSTRUCTOR) vec_safe_push (ctors, ce->value); ggc_free (elts); } ggc_free (c); } } /* 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 % " "function %q+#D", DECL_PARM_INDEX (parm), fun); } } return ret; } void explain_invalid_constexpr_fn (tree fun) { static hash_set *diagnosed; tree body; if (diagnosed == NULL) diagnosed = new hash_set; 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 % 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::create_ggc (101); constexpr_fundef **slot = constexpr_fundef_table->find_slot ( const_cast (&value), INSERT); gcc_assert (*slot == NULL); *slot = ggc_alloc (); **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 = eval_constant_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; } // forked from gcc/cp/constexpr.cc cxx_eval_conditional_expression /* Subroutine of cxx_eval_constant_expression. Attempt to evaluate condition expressions. Dead branches are not looked into. */ static tree eval_conditional_expression (const constexpr_ctx *ctx, tree t, bool lval, bool *non_constant_p, bool *overflow_p, tree *jump_target) { tree val = eval_constant_expression (ctx, TREE_OPERAND (t, 0), /*lval*/ false, non_constant_p, overflow_p); VERIFY_CONSTANT (val); if (TREE_CODE (t) == IF_STMT && IF_STMT_CONSTEVAL_P (t)) { /* Evaluate the condition as if it was if (__builtin_is_constant_evaluated ()), i.e. defer it if not ctx->manifestly_const_eval (as sometimes we try to constant evaluate without manifestly_const_eval even expressions or parts thereof which will later be manifestly const_eval evaluated), otherwise fold it to true. */ if (ctx->manifestly_const_eval) val = boolean_true_node; else { *non_constant_p = true; return t; } } /* Don't VERIFY_CONSTANT the other operands. */ if (integer_zerop (val)) val = TREE_OPERAND (t, 2); else val = TREE_OPERAND (t, 1); if (TREE_CODE (t) == IF_STMT && !val) val = void_node; return eval_constant_expression (ctx, val, lval, non_constant_p, overflow_p, jump_target); } // #include "gt-rust-rust-constexpr.h" } // namespace Compile } // namespace Rust