// 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"
namespace Rust {
namespace Compile {
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
get_function_named_in_call (tree t);
ConstCtx::ConstCtx () : constexpr_ops_count (0) {}
tree
ConstCtx::fold (tree expr)
{
tree folded = ConstCtx ().constexpr_expression (expr);
rust_assert (folded != NULL_TREE);
return folded;
}
tree
ConstCtx::constexpr_expression (tree t)
{
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 (++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 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 (t);
break;
case CALL_EXPR:
r = eval_call_expression (t);
break;
case RETURN_EXPR:
rust_assert (TREE_OPERAND (t, 0) != NULL_TREE);
r = constexpr_expression (TREE_OPERAND (t, 0));
break;
case MODIFY_EXPR:
r = eval_store_expression (t);
break;
default:
break;
}
return r;
}
tree
ConstCtx::eval_store_expression (tree t)
{
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. */
init = ConstCtx::fold (init);
}
bool evaluated = false;
tree object = NULL_TREE;
for (tree probe = target; object == NULL_TREE;)
{
switch (TREE_CODE (probe))
{
default:
if (evaluated)
object = probe;
else
{
probe = constexpr_expression (probe);
evaluated = true;
}
break;
}
}
return init;
}
/* Subroutine of cxx_eval_constant_expression.
Like cxx_eval_unary_expression, except for binary expressions. */
tree
ConstCtx::eval_binary_expression (tree t)
{
tree orig_lhs = TREE_OPERAND (t, 0);
tree orig_rhs = TREE_OPERAND (t, 1);
tree lhs, rhs;
lhs = constexpr_expression (orig_lhs);
rhs = constexpr_expression (orig_rhs);
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);
}
// Subroutine of cxx_eval_constant_expression.
// Evaluate the call expression tree T in the context of OLD_CALL expression
// evaluation.
tree
ConstCtx::eval_call_expression (tree t)
{
tree fun = get_function_named_in_call (t);
return constexpr_fn_retval (DECL_SAVED_TREE (fun));
}
// 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.
tree
ConstCtx::constexpr_fn_retval (tree body)
{
switch (TREE_CODE (body))
{
case STATEMENT_LIST: {
tree expr = NULL_TREE;
for (tree stmt : tsi_range (body))
{
tree s = constexpr_fn_retval (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:
return constexpr_expression (body);
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 (TREE_OPERAND (body, 0));
case BIND_EXPR: {
tree b = BIND_EXPR_BODY (body);
return constexpr_fn_retval (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;
}
} // namespace Compile
} // namespace Rust