blob: 73c12e3bb8c39907b6bd95148f860709dbbf3f50 [file] [log] [blame]
/* Expand builtin functions.
Copyright (C) 1988-2021 Free Software Foundation, Inc.
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
<http://www.gnu.org/licenses/>. */
/* Legacy warning! Please add no further builtin simplifications here
(apart from pure constant folding) - builtin simplifications should go
to match.pd or gimple-fold.c instead. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "target.h"
#include "rtl.h"
#include "tree.h"
#include "memmodel.h"
#include "gimple.h"
#include "predict.h"
#include "tm_p.h"
#include "stringpool.h"
#include "tree-vrp.h"
#include "tree-ssanames.h"
#include "expmed.h"
#include "optabs.h"
#include "emit-rtl.h"
#include "recog.h"
#include "diagnostic-core.h"
#include "alias.h"
#include "fold-const.h"
#include "fold-const-call.h"
#include "gimple-ssa-warn-restrict.h"
#include "stor-layout.h"
#include "calls.h"
#include "varasm.h"
#include "tree-object-size.h"
#include "tree-ssa-strlen.h"
#include "realmpfr.h"
#include "cfgrtl.h"
#include "except.h"
#include "dojump.h"
#include "explow.h"
#include "stmt.h"
#include "expr.h"
#include "libfuncs.h"
#include "output.h"
#include "typeclass.h"
#include "langhooks.h"
#include "value-prof.h"
#include "builtins.h"
#include "stringpool.h"
#include "attribs.h"
#include "asan.h"
#include "internal-fn.h"
#include "case-cfn-macros.h"
#include "gimple-fold.h"
#include "intl.h"
#include "file-prefix-map.h" /* remap_macro_filename() */
#include "gomp-constants.h"
#include "omp-general.h"
#include "tree-dfa.h"
#include "gimple-iterator.h"
#include "gimple-ssa.h"
#include "tree-ssa-live.h"
#include "tree-outof-ssa.h"
#include "attr-fnspec.h"
#include "demangle.h"
struct target_builtins default_target_builtins;
#if SWITCHABLE_TARGET
struct target_builtins *this_target_builtins = &default_target_builtins;
#endif
/* Define the names of the builtin function types and codes. */
const char *const built_in_class_names[BUILT_IN_LAST]
= {"NOT_BUILT_IN", "BUILT_IN_FRONTEND", "BUILT_IN_MD", "BUILT_IN_NORMAL"};
#define DEF_BUILTIN(X, N, C, T, LT, B, F, NA, AT, IM, COND) #X,
const char * built_in_names[(int) END_BUILTINS] =
{
#include "builtins.def"
};
/* Setup an array of builtin_info_type, make sure each element decl is
initialized to NULL_TREE. */
builtin_info_type builtin_info[(int)END_BUILTINS];
/* Non-zero if __builtin_constant_p should be folded right away. */
bool force_folding_builtin_constant_p;
static int target_char_cast (tree, char *);
static rtx get_memory_rtx (tree, tree);
static int apply_args_size (void);
static int apply_result_size (void);
static rtx result_vector (int, rtx);
static void expand_builtin_prefetch (tree);
static rtx expand_builtin_apply_args (void);
static rtx expand_builtin_apply_args_1 (void);
static rtx expand_builtin_apply (rtx, rtx, rtx);
static void expand_builtin_return (rtx);
static enum type_class type_to_class (tree);
static rtx expand_builtin_classify_type (tree);
static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
static rtx expand_builtin_interclass_mathfn (tree, rtx);
static rtx expand_builtin_sincos (tree);
static rtx expand_builtin_cexpi (tree, rtx);
static rtx expand_builtin_int_roundingfn (tree, rtx);
static rtx expand_builtin_int_roundingfn_2 (tree, rtx);
static rtx expand_builtin_next_arg (void);
static rtx expand_builtin_va_start (tree);
static rtx expand_builtin_va_end (tree);
static rtx expand_builtin_va_copy (tree);
static rtx inline_expand_builtin_bytecmp (tree, rtx);
static rtx expand_builtin_strcmp (tree, rtx);
static rtx expand_builtin_strncmp (tree, rtx, machine_mode);
static rtx builtin_memcpy_read_str (void *, HOST_WIDE_INT, scalar_int_mode);
static rtx expand_builtin_memchr (tree, rtx);
static rtx expand_builtin_memcpy (tree, rtx);
static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len,
rtx target, tree exp,
memop_ret retmode,
bool might_overlap);
static rtx expand_builtin_memmove (tree, rtx);
static rtx expand_builtin_mempcpy (tree, rtx);
static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx, tree, memop_ret);
static rtx expand_builtin_strcat (tree);
static rtx expand_builtin_strcpy (tree, rtx);
static rtx expand_builtin_strcpy_args (tree, tree, tree, rtx);
static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
static rtx expand_builtin_stpncpy (tree, rtx);
static rtx expand_builtin_strncat (tree, rtx);
static rtx expand_builtin_strncpy (tree, rtx);
static rtx builtin_memset_gen_str (void *, HOST_WIDE_INT, scalar_int_mode);
static rtx expand_builtin_memset (tree, rtx, machine_mode);
static rtx expand_builtin_memset_args (tree, tree, tree, rtx, machine_mode, tree);
static rtx expand_builtin_bzero (tree);
static rtx expand_builtin_strlen (tree, rtx, machine_mode);
static rtx expand_builtin_strnlen (tree, rtx, machine_mode);
static rtx expand_builtin_alloca (tree);
static rtx expand_builtin_unop (machine_mode, tree, rtx, rtx, optab);
static rtx expand_builtin_frame_address (tree, tree);
static tree stabilize_va_list_loc (location_t, tree, int);
static rtx expand_builtin_expect (tree, rtx);
static rtx expand_builtin_expect_with_probability (tree, rtx);
static tree fold_builtin_constant_p (tree);
static tree fold_builtin_classify_type (tree);
static tree fold_builtin_strlen (location_t, tree, tree, tree);
static tree fold_builtin_inf (location_t, tree, int);
static tree rewrite_call_expr (location_t, tree, int, tree, int, ...);
static bool validate_arg (const_tree, enum tree_code code);
static rtx expand_builtin_fabs (tree, rtx, rtx);
static rtx expand_builtin_signbit (tree, rtx);
static tree fold_builtin_memcmp (location_t, tree, tree, tree);
static tree fold_builtin_isascii (location_t, tree);
static tree fold_builtin_toascii (location_t, tree);
static tree fold_builtin_isdigit (location_t, tree);
static tree fold_builtin_fabs (location_t, tree, tree);
static tree fold_builtin_abs (location_t, tree, tree);
static tree fold_builtin_unordered_cmp (location_t, tree, tree, tree, enum tree_code,
enum tree_code);
static tree fold_builtin_varargs (location_t, tree, tree*, int);
static tree fold_builtin_strpbrk (location_t, tree, tree, tree, tree);
static tree fold_builtin_strspn (location_t, tree, tree, tree);
static tree fold_builtin_strcspn (location_t, tree, tree, tree);
static rtx expand_builtin_object_size (tree);
static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
enum built_in_function);
static void maybe_emit_chk_warning (tree, enum built_in_function);
static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
static tree fold_builtin_object_size (tree, tree);
static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &,
pointer_query *);
unsigned HOST_WIDE_INT target_newline;
unsigned HOST_WIDE_INT target_percent;
static unsigned HOST_WIDE_INT target_c;
static unsigned HOST_WIDE_INT target_s;
char target_percent_c[3];
char target_percent_s[3];
char target_percent_s_newline[4];
static tree do_mpfr_remquo (tree, tree, tree);
static tree do_mpfr_lgamma_r (tree, tree, tree);
static void expand_builtin_sync_synchronize (void);
access_ref::access_ref (tree bound /* = NULL_TREE */,
bool minaccess /* = false */)
: ref (), eval ([](tree x){ return x; }), deref (), trail1special (true),
base0 (true), parmarray ()
{
/* Set to valid. */
offrng[0] = offrng[1] = 0;
/* Invalidate. */
sizrng[0] = sizrng[1] = -1;
/* Set the default bounds of the access and adjust below. */
bndrng[0] = minaccess ? 1 : 0;
bndrng[1] = HOST_WIDE_INT_M1U;
/* When BOUND is nonnull and a range can be extracted from it,
set the bounds of the access to reflect both it and MINACCESS.
BNDRNG[0] is the size of the minimum access. */
tree rng[2];
if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO))
{
bndrng[0] = wi::to_offset (rng[0]);
bndrng[1] = wi::to_offset (rng[1]);
bndrng[0] = bndrng[0] > 0 && minaccess ? 1 : 0;
}
}
/* Return the PHI node REF refers to or null if it doesn't. */
gphi *
access_ref::phi () const
{
if (!ref || TREE_CODE (ref) != SSA_NAME)
return NULL;
gimple *def_stmt = SSA_NAME_DEF_STMT (ref);
if (gimple_code (def_stmt) != GIMPLE_PHI)
return NULL;
return as_a <gphi *> (def_stmt);
}
/* Determine and return the largest object to which *THIS. If *THIS
refers to a PHI and PREF is nonnull, fill *PREF with the details
of the object determined by compute_objsize(ARG, OSTYPE) for each
PHI argument ARG. */
tree
access_ref::get_ref (vec<access_ref> *all_refs,
access_ref *pref /* = NULL */,
int ostype /* = 1 */,
ssa_name_limit_t *psnlim /* = NULL */,
pointer_query *qry /* = NULL */) const
{
gphi *phi_stmt = this->phi ();
if (!phi_stmt)
return ref;
/* FIXME: Calling get_ref() with a null PSNLIM is dangerous and might
cause unbounded recursion. */
ssa_name_limit_t snlim_buf;
if (!psnlim)
psnlim = &snlim_buf;
if (!psnlim->visit_phi (ref))
return NULL_TREE;
/* Reflects the range of offsets of all PHI arguments refer to the same
object (i.e., have the same REF). */
access_ref same_ref;
/* The conservative result of the PHI reflecting the offset and size
of the largest PHI argument, regardless of whether or not they all
refer to the same object. */
pointer_query empty_qry;
if (!qry)
qry = &empty_qry;
access_ref phi_ref;
if (pref)
{
phi_ref = *pref;
same_ref = *pref;
}
/* Set if any argument is a function array (or VLA) parameter not
declared [static]. */
bool parmarray = false;
/* The size of the smallest object referenced by the PHI arguments. */
offset_int minsize = 0;
const offset_int maxobjsize = wi::to_offset (max_object_size ());
/* The offset of the PHI, not reflecting those of its arguments. */
const offset_int orng[2] = { phi_ref.offrng[0], phi_ref.offrng[1] };
const unsigned nargs = gimple_phi_num_args (phi_stmt);
for (unsigned i = 0; i < nargs; ++i)
{
access_ref phi_arg_ref;
tree arg = gimple_phi_arg_def (phi_stmt, i);
if (!compute_objsize_r (arg, ostype, &phi_arg_ref, *psnlim, qry)
|| phi_arg_ref.sizrng[0] < 0)
/* A PHI with all null pointer arguments. */
return NULL_TREE;
/* Add PREF's offset to that of the argument. */
phi_arg_ref.add_offset (orng[0], orng[1]);
if (TREE_CODE (arg) == SSA_NAME)
qry->put_ref (arg, phi_arg_ref);
if (all_refs)
all_refs->safe_push (phi_arg_ref);
const bool arg_known_size = (phi_arg_ref.sizrng[0] != 0
|| phi_arg_ref.sizrng[1] != maxobjsize);
parmarray |= phi_arg_ref.parmarray;
const bool nullp = integer_zerop (arg) && (i || i + 1 < nargs);
if (phi_ref.sizrng[0] < 0)
{
if (!nullp)
same_ref = phi_arg_ref;
phi_ref = phi_arg_ref;
if (arg_known_size)
minsize = phi_arg_ref.sizrng[0];
continue;
}
const bool phi_known_size = (phi_ref.sizrng[0] != 0
|| phi_ref.sizrng[1] != maxobjsize);
if (phi_known_size && phi_arg_ref.sizrng[0] < minsize)
minsize = phi_arg_ref.sizrng[0];
/* Disregard null pointers in PHIs with two or more arguments.
TODO: Handle this better! */
if (nullp)
continue;
/* Determine the amount of remaining space in the argument. */
offset_int argrem[2];
argrem[1] = phi_arg_ref.size_remaining (argrem);
/* Determine the amount of remaining space computed so far and
if the remaining space in the argument is more use it instead. */
offset_int phirem[2];
phirem[1] = phi_ref.size_remaining (phirem);
if (phi_arg_ref.ref != same_ref.ref)
same_ref.ref = NULL_TREE;
if (phirem[1] < argrem[1]
|| (phirem[1] == argrem[1]
&& phi_ref.sizrng[1] < phi_arg_ref.sizrng[1]))
/* Use the argument with the most space remaining as the result,
or the larger one if the space is equal. */
phi_ref = phi_arg_ref;
/* Set SAME_REF.OFFRNG to the maximum range of all arguments. */
if (phi_arg_ref.offrng[0] < same_ref.offrng[0])
same_ref.offrng[0] = phi_arg_ref.offrng[0];
if (same_ref.offrng[1] < phi_arg_ref.offrng[1])
same_ref.offrng[1] = phi_arg_ref.offrng[1];
}
if (!same_ref.ref && same_ref.offrng[0] != 0)
/* Clear BASE0 if not all the arguments refer to the same object and
if not all their offsets are zero-based. This allows the final
PHI offset to out of bounds for some arguments but not for others
(or negative even of all the arguments are BASE0), which is overly
permissive. */
phi_ref.base0 = false;
if (same_ref.ref)
phi_ref = same_ref;
else
{
/* Replace the lower bound of the largest argument with the size
of the smallest argument, and set PARMARRAY if any argument
was one. */
phi_ref.sizrng[0] = minsize;
phi_ref.parmarray = parmarray;
}
if (phi_ref.sizrng[0] < 0)
{
/* Fail if none of the PHI's arguments resulted in updating PHI_REF
(perhaps because they have all been already visited by prior
recursive calls). */
psnlim->leave_phi (ref);
return NULL_TREE;
}
/* Avoid changing *THIS. */
if (pref && pref != this)
*pref = phi_ref;
psnlim->leave_phi (ref);
return phi_ref.ref;
}
/* Return the maximum amount of space remaining and if non-null, set
argument to the minimum. */
offset_int
access_ref::size_remaining (offset_int *pmin /* = NULL */) const
{
offset_int minbuf;
if (!pmin)
pmin = &minbuf;
/* add_offset() ensures the offset range isn't inverted. */
gcc_checking_assert (offrng[0] <= offrng[1]);
if (base0)
{
/* The offset into referenced object is zero-based (i.e., it's
not referenced by a pointer into middle of some unknown object). */
if (offrng[0] < 0 && offrng[1] < 0)
{
/* If the offset is negative the remaining size is zero. */
*pmin = 0;
return 0;
}
if (sizrng[1] <= offrng[0])
{
/* If the starting offset is greater than or equal to the upper
bound on the size of the object, the space remaining is zero.
As a special case, if it's equal, set *PMIN to -1 to let
the caller know the offset is valid and just past the end. */
*pmin = sizrng[1] == offrng[0] ? -1 : 0;
return 0;
}
/* Otherwise return the size minus the lower bound of the offset. */
offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
*pmin = sizrng[0] - or0;
return sizrng[1] - or0;
}
/* The offset to the referenced object isn't zero-based (i.e., it may
refer to a byte other than the first. The size of such an object
is constrained only by the size of the address space (the result
of max_object_size()). */
if (sizrng[1] <= offrng[0])
{
*pmin = 0;
return 0;
}
offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
*pmin = sizrng[0] - or0;
return sizrng[1] - or0;
}
/* Add the range [MIN, MAX] to the offset range. For known objects (with
zero-based offsets) at least one of whose offset's bounds is in range,
constrain the other (or both) to the bounds of the object (i.e., zero
and the upper bound of its size). This improves the quality of
diagnostics. */
void access_ref::add_offset (const offset_int &min, const offset_int &max)
{
if (min <= max)
{
/* To add an ordinary range just add it to the bounds. */
offrng[0] += min;
offrng[1] += max;
}
else if (!base0)
{
/* To add an inverted range to an offset to an unknown object
expand it to the maximum. */
add_max_offset ();
return;
}
else
{
/* To add an inverted range to an offset to an known object set
the upper bound to the maximum representable offset value
(which may be greater than MAX_OBJECT_SIZE).
The lower bound is either the sum of the current offset and
MIN when abs(MAX) is greater than the former, or zero otherwise.
Zero because then then inverted range includes the negative of
the lower bound. */
offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
offrng[1] = maxoff;
if (max >= 0)
{
offrng[0] = 0;
return;
}
offset_int absmax = wi::abs (max);
if (offrng[0] < absmax)
{
offrng[0] += min;
/* Cap the lower bound at the upper (set to MAXOFF above)
to avoid inadvertently recreating an inverted range. */
if (offrng[1] < offrng[0])
offrng[0] = offrng[1];
}
else
offrng[0] = 0;
}
if (!base0)
return;
/* When referencing a known object check to see if the offset computed
so far is in bounds... */
offset_int remrng[2];
remrng[1] = size_remaining (remrng);
if (remrng[1] > 0 || remrng[0] < 0)
{
/* ...if so, constrain it so that neither bound exceeds the size of
the object. Out of bounds offsets are left unchanged, and, for
better or worse, become in bounds later. They should be detected
and diagnosed at the point they first become invalid by
-Warray-bounds. */
if (offrng[0] < 0)
offrng[0] = 0;
if (offrng[1] > sizrng[1])
offrng[1] = sizrng[1];
}
}
/* Set a bit for the PHI in VISITED and return true if it wasn't
already set. */
bool
ssa_name_limit_t::visit_phi (tree ssa_name)
{
if (!visited)
visited = BITMAP_ALLOC (NULL);
/* Return false if SSA_NAME has already been visited. */
return bitmap_set_bit (visited, SSA_NAME_VERSION (ssa_name));
}
/* Clear a bit for the PHI in VISITED. */
void
ssa_name_limit_t::leave_phi (tree ssa_name)
{
/* Return false if SSA_NAME has already been visited. */
bitmap_clear_bit (visited, SSA_NAME_VERSION (ssa_name));
}
/* Return false if the SSA_NAME chain length counter has reached
the limit, otherwise increment the counter and return true. */
bool
ssa_name_limit_t::next ()
{
/* Return a negative value to let caller avoid recursing beyond
the specified limit. */
if (ssa_def_max == 0)
return false;
--ssa_def_max;
return true;
}
/* If the SSA_NAME has already been "seen" return a positive value.
Otherwise add it to VISITED. If the SSA_NAME limit has been
reached, return a negative value. Otherwise return zero. */
int
ssa_name_limit_t::next_phi (tree ssa_name)
{
{
gimple *def_stmt = SSA_NAME_DEF_STMT (ssa_name);
/* Return a positive value if the PHI has already been visited. */
if (gimple_code (def_stmt) == GIMPLE_PHI
&& !visit_phi (ssa_name))
return 1;
}
/* Return a negative value to let caller avoid recursing beyond
the specified limit. */
if (ssa_def_max == 0)
return -1;
--ssa_def_max;
return 0;
}
ssa_name_limit_t::~ssa_name_limit_t ()
{
if (visited)
BITMAP_FREE (visited);
}
/* Default ctor. Initialize object with pointers to the range_query
and cache_type instances to use or null. */
pointer_query::pointer_query (range_query *qry /* = NULL */,
cache_type *cache /* = NULL */)
: rvals (qry), var_cache (cache), hits (), misses (),
failures (), depth (), max_depth ()
{
/* No op. */
}
/* Return a pointer to the cached access_ref instance for the SSA_NAME
PTR if it's there or null otherwise. */
const access_ref *
pointer_query::get_ref (tree ptr, int ostype /* = 1 */) const
{
if (!var_cache)
{
++misses;
return NULL;
}
unsigned version = SSA_NAME_VERSION (ptr);
unsigned idx = version << 1 | (ostype & 1);
if (var_cache->indices.length () <= idx)
{
++misses;
return NULL;
}
unsigned cache_idx = var_cache->indices[idx];
if (var_cache->access_refs.length () <= cache_idx)
{
++misses;
return NULL;
}
access_ref &cache_ref = var_cache->access_refs[cache_idx];
if (cache_ref.ref)
{
++hits;
return &cache_ref;
}
++misses;
return NULL;
}
/* Retrieve the access_ref instance for a variable from the cache if it's
there or compute it and insert it into the cache if it's nonnonull. */
bool
pointer_query::get_ref (tree ptr, access_ref *pref, int ostype /* = 1 */)
{
const unsigned version
= TREE_CODE (ptr) == SSA_NAME ? SSA_NAME_VERSION (ptr) : 0;
if (var_cache && version)
{
unsigned idx = version << 1 | (ostype & 1);
if (idx < var_cache->indices.length ())
{
unsigned cache_idx = var_cache->indices[idx] - 1;
if (cache_idx < var_cache->access_refs.length ()
&& var_cache->access_refs[cache_idx].ref)
{
++hits;
*pref = var_cache->access_refs[cache_idx];
return true;
}
}
++misses;
}
if (!compute_objsize (ptr, ostype, pref, this))
{
++failures;
return false;
}
return true;
}
/* Add a copy of the access_ref REF for the SSA_NAME to the cache if it's
nonnull. */
void
pointer_query::put_ref (tree ptr, const access_ref &ref, int ostype /* = 1 */)
{
/* Only add populated/valid entries. */
if (!var_cache || !ref.ref || ref.sizrng[0] < 0)
return;
/* Add REF to the two-level cache. */
unsigned version = SSA_NAME_VERSION (ptr);
unsigned idx = version << 1 | (ostype & 1);
/* Grow INDICES if necessary. An index is valid if it's nonzero.
Its value minus one is the index into ACCESS_REFS. Not all
entries are valid. */
if (var_cache->indices.length () <= idx)
var_cache->indices.safe_grow_cleared (idx + 1);
if (!var_cache->indices[idx])
var_cache->indices[idx] = var_cache->access_refs.length () + 1;
/* Grow ACCESS_REF cache if necessary. An entry is valid if its
REF member is nonnull. All entries except for the last two
are valid. Once nonnull, the REF value must stay unchanged. */
unsigned cache_idx = var_cache->indices[idx];
if (var_cache->access_refs.length () <= cache_idx)
var_cache->access_refs.safe_grow_cleared (cache_idx + 1);
access_ref cache_ref = var_cache->access_refs[cache_idx - 1];
if (cache_ref.ref)
{
gcc_checking_assert (cache_ref.ref == ref.ref);
return;
}
cache_ref = ref;
}
/* Flush the cache if it's nonnull. */
void
pointer_query::flush_cache ()
{
if (!var_cache)
return;
var_cache->indices.release ();
var_cache->access_refs.release ();
}
/* Return true if NAME starts with __builtin_ or __sync_. */
static bool
is_builtin_name (const char *name)
{
if (strncmp (name, "__builtin_", 10) == 0)
return true;
if (strncmp (name, "__sync_", 7) == 0)
return true;
if (strncmp (name, "__atomic_", 9) == 0)
return true;
return false;
}
/* Return true if NODE should be considered for inline expansion regardless
of the optimization level. This means whenever a function is invoked with
its "internal" name, which normally contains the prefix "__builtin". */
bool
called_as_built_in (tree node)
{
/* Note that we must use DECL_NAME, not DECL_ASSEMBLER_NAME_SET_P since
we want the name used to call the function, not the name it
will have. */
const char *name = IDENTIFIER_POINTER (DECL_NAME (node));
return is_builtin_name (name);
}
/* Compute values M and N such that M divides (address of EXP - N) and such
that N < M. If these numbers can be determined, store M in alignp and N in
*BITPOSP and return true. Otherwise return false and store BITS_PER_UNIT to
*alignp and any bit-offset to *bitposp.
Note that the address (and thus the alignment) computed here is based
on the address to which a symbol resolves, whereas DECL_ALIGN is based
on the address at which an object is actually located. These two
addresses are not always the same. For example, on ARM targets,
the address &foo of a Thumb function foo() has the lowest bit set,
whereas foo() itself starts on an even address.
If ADDR_P is true we are taking the address of the memory reference EXP
and thus cannot rely on the access taking place. */
static bool
get_object_alignment_2 (tree exp, unsigned int *alignp,
unsigned HOST_WIDE_INT *bitposp, bool addr_p)
{
poly_int64 bitsize, bitpos;
tree offset;
machine_mode mode;
int unsignedp, reversep, volatilep;
unsigned int align = BITS_PER_UNIT;
bool known_alignment = false;
/* Get the innermost object and the constant (bitpos) and possibly
variable (offset) offset of the access. */
exp = get_inner_reference (exp, &bitsize, &bitpos, &offset, &mode,
&unsignedp, &reversep, &volatilep);
/* Extract alignment information from the innermost object and
possibly adjust bitpos and offset. */
if (TREE_CODE (exp) == FUNCTION_DECL)
{
/* Function addresses can encode extra information besides their
alignment. However, if TARGET_PTRMEMFUNC_VBIT_LOCATION
allows the low bit to be used as a virtual bit, we know
that the address itself must be at least 2-byte aligned. */
if (TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn)
align = 2 * BITS_PER_UNIT;
}
else if (TREE_CODE (exp) == LABEL_DECL)
;
else if (TREE_CODE (exp) == CONST_DECL)
{
/* The alignment of a CONST_DECL is determined by its initializer. */
exp = DECL_INITIAL (exp);
align = TYPE_ALIGN (TREE_TYPE (exp));
if (CONSTANT_CLASS_P (exp))
align = targetm.constant_alignment (exp, align);
known_alignment = true;
}
else if (DECL_P (exp))
{
align = DECL_ALIGN (exp);
known_alignment = true;
}
else if (TREE_CODE (exp) == INDIRECT_REF
|| TREE_CODE (exp) == MEM_REF
|| TREE_CODE (exp) == TARGET_MEM_REF)
{
tree addr = TREE_OPERAND (exp, 0);
unsigned ptr_align;
unsigned HOST_WIDE_INT ptr_bitpos;
unsigned HOST_WIDE_INT ptr_bitmask = ~0;
/* If the address is explicitely aligned, handle that. */
if (TREE_CODE (addr) == BIT_AND_EXPR
&& TREE_CODE (TREE_OPERAND (addr, 1)) == INTEGER_CST)
{
ptr_bitmask = TREE_INT_CST_LOW (TREE_OPERAND (addr, 1));
ptr_bitmask *= BITS_PER_UNIT;
align = least_bit_hwi (ptr_bitmask);
addr = TREE_OPERAND (addr, 0);
}
known_alignment
= get_pointer_alignment_1 (addr, &ptr_align, &ptr_bitpos);
align = MAX (ptr_align, align);
/* Re-apply explicit alignment to the bitpos. */
ptr_bitpos &= ptr_bitmask;
/* The alignment of the pointer operand in a TARGET_MEM_REF
has to take the variable offset parts into account. */
if (TREE_CODE (exp) == TARGET_MEM_REF)
{
if (TMR_INDEX (exp))
{
unsigned HOST_WIDE_INT step = 1;
if (TMR_STEP (exp))
step = TREE_INT_CST_LOW (TMR_STEP (exp));
align = MIN (align, least_bit_hwi (step) * BITS_PER_UNIT);
}
if (TMR_INDEX2 (exp))
align = BITS_PER_UNIT;
known_alignment = false;
}
/* When EXP is an actual memory reference then we can use
TYPE_ALIGN of a pointer indirection to derive alignment.
Do so only if get_pointer_alignment_1 did not reveal absolute
alignment knowledge and if using that alignment would
improve the situation. */
unsigned int talign;
if (!addr_p && !known_alignment
&& (talign = min_align_of_type (TREE_TYPE (exp)) * BITS_PER_UNIT)
&& talign > align)
align = talign;
else
{
/* Else adjust bitpos accordingly. */
bitpos += ptr_bitpos;
if (TREE_CODE (exp) == MEM_REF
|| TREE_CODE (exp) == TARGET_MEM_REF)
bitpos += mem_ref_offset (exp).force_shwi () * BITS_PER_UNIT;
}
}
else if (TREE_CODE (exp) == STRING_CST)
{
/* STRING_CST are the only constant objects we allow to be not
wrapped inside a CONST_DECL. */
align = TYPE_ALIGN (TREE_TYPE (exp));
if (CONSTANT_CLASS_P (exp))
align = targetm.constant_alignment (exp, align);
known_alignment = true;
}
/* If there is a non-constant offset part extract the maximum
alignment that can prevail. */
if (offset)
{
unsigned int trailing_zeros = tree_ctz (offset);
if (trailing_zeros < HOST_BITS_PER_INT)
{
unsigned int inner = (1U << trailing_zeros) * BITS_PER_UNIT;
if (inner)
align = MIN (align, inner);
}
}
/* Account for the alignment of runtime coefficients, so that the constant
bitpos is guaranteed to be accurate. */
unsigned int alt_align = ::known_alignment (bitpos - bitpos.coeffs[0]);
if (alt_align != 0 && alt_align < align)
{
align = alt_align;
known_alignment = false;
}
*alignp = align;
*bitposp = bitpos.coeffs[0] & (align - 1);
return known_alignment;
}
/* For a memory reference expression EXP compute values M and N such that M
divides (&EXP - N) and such that N < M. If these numbers can be determined,
store M in alignp and N in *BITPOSP and return true. Otherwise return false
and store BITS_PER_UNIT to *alignp and any bit-offset to *bitposp. */
bool
get_object_alignment_1 (tree exp, unsigned int *alignp,
unsigned HOST_WIDE_INT *bitposp)
{
return get_object_alignment_2 (exp, alignp, bitposp, false);
}
/* Return the alignment in bits of EXP, an object. */
unsigned int
get_object_alignment (tree exp)
{
unsigned HOST_WIDE_INT bitpos = 0;
unsigned int align;
get_object_alignment_1 (exp, &align, &bitpos);
/* align and bitpos now specify known low bits of the pointer.
ptr & (align - 1) == bitpos. */
if (bitpos != 0)
align = least_bit_hwi (bitpos);
return align;
}
/* For a pointer valued expression EXP compute values M and N such that M
divides (EXP - N) and such that N < M. If these numbers can be determined,
store M in alignp and N in *BITPOSP and return true. Return false if
the results are just a conservative approximation.
If EXP is not a pointer, false is returned too. */
bool
get_pointer_alignment_1 (tree exp, unsigned int *alignp,
unsigned HOST_WIDE_INT *bitposp)
{
STRIP_NOPS (exp);
if (TREE_CODE (exp) == ADDR_EXPR)
return get_object_alignment_2 (TREE_OPERAND (exp, 0),
alignp, bitposp, true);
else if (TREE_CODE (exp) == POINTER_PLUS_EXPR)
{
unsigned int align;
unsigned HOST_WIDE_INT bitpos;
bool res = get_pointer_alignment_1 (TREE_OPERAND (exp, 0),
&align, &bitpos);
if (TREE_CODE (TREE_OPERAND (exp, 1)) == INTEGER_CST)
bitpos += TREE_INT_CST_LOW (TREE_OPERAND (exp, 1)) * BITS_PER_UNIT;
else
{
unsigned int trailing_zeros = tree_ctz (TREE_OPERAND (exp, 1));
if (trailing_zeros < HOST_BITS_PER_INT)
{
unsigned int inner = (1U << trailing_zeros) * BITS_PER_UNIT;
if (inner)
align = MIN (align, inner);
}
}
*alignp = align;
*bitposp = bitpos & (align - 1);
return res;
}
else if (TREE_CODE (exp) == SSA_NAME
&& POINTER_TYPE_P (TREE_TYPE (exp)))
{
unsigned int ptr_align, ptr_misalign;
struct ptr_info_def *pi = SSA_NAME_PTR_INFO (exp);
if (pi && get_ptr_info_alignment (pi, &ptr_align, &ptr_misalign))
{
*bitposp = ptr_misalign * BITS_PER_UNIT;
*alignp = ptr_align * BITS_PER_UNIT;
/* Make sure to return a sensible alignment when the multiplication
by BITS_PER_UNIT overflowed. */
if (*alignp == 0)
*alignp = 1u << (HOST_BITS_PER_INT - 1);
/* We cannot really tell whether this result is an approximation. */
return false;
}
else
{
*bitposp = 0;
*alignp = BITS_PER_UNIT;
return false;
}
}
else if (TREE_CODE (exp) == INTEGER_CST)
{
*alignp = BIGGEST_ALIGNMENT;
*bitposp = ((TREE_INT_CST_LOW (exp) * BITS_PER_UNIT)
& (BIGGEST_ALIGNMENT - 1));
return true;
}
*bitposp = 0;
*alignp = BITS_PER_UNIT;
return false;
}
/* Return the alignment in bits of EXP, a pointer valued expression.
The alignment returned is, by default, the alignment of the thing that
EXP points to. If it is not a POINTER_TYPE, 0 is returned.
Otherwise, look at the expression to see if we can do better, i.e., if the
expression is actually pointing at an object whose alignment is tighter. */
unsigned int
get_pointer_alignment (tree exp)
{
unsigned HOST_WIDE_INT bitpos = 0;
unsigned int align;
get_pointer_alignment_1 (exp, &align, &bitpos);
/* align and bitpos now specify known low bits of the pointer.
ptr & (align - 1) == bitpos. */
if (bitpos != 0)
align = least_bit_hwi (bitpos);
return align;
}
/* Return the number of leading non-zero elements in the sequence
[ PTR, PTR + MAXELTS ) where each element's size is ELTSIZE bytes.
ELTSIZE must be a power of 2 less than 8. Used by c_strlen. */
unsigned
string_length (const void *ptr, unsigned eltsize, unsigned maxelts)
{
gcc_checking_assert (eltsize == 1 || eltsize == 2 || eltsize == 4);
unsigned n;
if (eltsize == 1)
{
/* Optimize the common case of plain char. */
for (n = 0; n < maxelts; n++)
{
const char *elt = (const char*) ptr + n;
if (!*elt)
break;
}
}
else
{
for (n = 0; n < maxelts; n++)
{
const char *elt = (const char*) ptr + n * eltsize;
if (!memcmp (elt, "\0\0\0\0", eltsize))
break;
}
}
return n;
}
/* For a call EXPR at LOC to a function FNAME that expects a string
in the argument ARG, issue a diagnostic due to it being a called
with an argument that is a character array with no terminating
NUL. SIZE is the EXACT size of the array, and BNDRNG the number
of characters in which the NUL is expected. Either EXPR or FNAME
may be null but noth both. SIZE may be null when BNDRNG is null. */
void
warn_string_no_nul (location_t loc, tree expr, const char *fname,
tree arg, tree decl, tree size /* = NULL_TREE */,
bool exact /* = false */,
const wide_int bndrng[2] /* = NULL */)
{
if ((expr && TREE_NO_WARNING (expr)) || TREE_NO_WARNING (arg))
return;
loc = expansion_point_location_if_in_system_header (loc);
bool warned;
/* Format the bound range as a string to keep the nuber of messages
from exploding. */
char bndstr[80];
*bndstr = 0;
if (bndrng)
{
if (bndrng[0] == bndrng[1])
sprintf (bndstr, "%llu", (unsigned long long) bndrng[0].to_uhwi ());
else
sprintf (bndstr, "[%llu, %llu]",
(unsigned long long) bndrng[0].to_uhwi (),
(unsigned long long) bndrng[1].to_uhwi ());
}
const tree maxobjsize = max_object_size ();
const wide_int maxsiz = wi::to_wide (maxobjsize);
if (expr)
{
tree func = get_callee_fndecl (expr);
if (bndrng)
{
if (wi::ltu_p (maxsiz, bndrng[0]))
warned = warning_at (loc, OPT_Wstringop_overread,
"%K%qD specified bound %s exceeds "
"maximum object size %E",
expr, func, bndstr, maxobjsize);
else
{
bool maybe = wi::to_wide (size) == bndrng[0];
warned = warning_at (loc, OPT_Wstringop_overread,
exact
? G_("%K%qD specified bound %s exceeds "
"the size %E of unterminated array")
: (maybe
? G_("%K%qD specified bound %s may "
"exceed the size of at most %E "
"of unterminated array")
: G_("%K%qD specified bound %s exceeds "
"the size of at most %E "
"of unterminated array")),
expr, func, bndstr, size);
}
}
else
warned = warning_at (loc, OPT_Wstringop_overread,
"%K%qD argument missing terminating nul",
expr, func);
}
else
{
if (bndrng)
{
if (wi::ltu_p (maxsiz, bndrng[0]))
warned = warning_at (loc, OPT_Wstringop_overread,
"%qs specified bound %s exceeds "
"maximum object size %E",
fname, bndstr, maxobjsize);
else
{
bool maybe = wi::to_wide (size) == bndrng[0];
warned = warning_at (loc, OPT_Wstringop_overread,
exact
? G_("%qs specified bound %s exceeds "
"the size %E of unterminated array")
: (maybe
? G_("%qs specified bound %s may "
"exceed the size of at most %E "
"of unterminated array")
: G_("%qs specified bound %s exceeds "
"the size of at most %E "
"of unterminated array")),
fname, bndstr, size);
}
}
else
warned = warning_at (loc, OPT_Wstringop_overread,
"%qs argument missing terminating nul",
fname);
}
if (warned)
{
inform (DECL_SOURCE_LOCATION (decl),
"referenced argument declared here");
TREE_NO_WARNING (arg) = 1;
if (expr)
TREE_NO_WARNING (expr) = 1;
}
}
/* For a call EXPR (which may be null) that expects a string argument
SRC as an argument, returns false if SRC is a character array with
no terminating NUL. When nonnull, BOUND is the number of characters
in which to expect the terminating NUL. RDONLY is true for read-only
accesses such as strcmp, false for read-write such as strcpy. When
EXPR is also issues a warning. */
bool
check_nul_terminated_array (tree expr, tree src,
tree bound /* = NULL_TREE */)
{
/* The constant size of the array SRC points to. The actual size
may be less of EXACT is true, but not more. */
tree size;
/* True if SRC involves a non-constant offset into the array. */
bool exact;
/* The unterminated constant array SRC points to. */
tree nonstr = unterminated_array (src, &size, &exact);
if (!nonstr)
return true;
/* NONSTR refers to the non-nul terminated constant array and SIZE
is the constant size of the array in bytes. EXACT is true when
SIZE is exact. */
wide_int bndrng[2];
if (bound)
{
if (TREE_CODE (bound) == INTEGER_CST)
bndrng[0] = bndrng[1] = wi::to_wide (bound);
else
{
value_range_kind rng = get_range_info (bound, bndrng, bndrng + 1);
if (rng != VR_RANGE)
return true;
}
if (exact)
{
if (wi::leu_p (bndrng[0], wi::to_wide (size)))
return true;
}
else if (wi::lt_p (bndrng[0], wi::to_wide (size), UNSIGNED))
return true;
}
if (expr)
warn_string_no_nul (EXPR_LOCATION (expr), expr, NULL, src, nonstr,
size, exact, bound ? bndrng : NULL);
return false;
}
/* If EXP refers to an unterminated constant character array return
the declaration of the object of which the array is a member or
element and if SIZE is not null, set *SIZE to the size of
the unterminated array and set *EXACT if the size is exact or
clear it otherwise. Otherwise return null. */
tree
unterminated_array (tree exp, tree *size /* = NULL */, bool *exact /* = NULL */)
{
/* C_STRLEN will return NULL and set DECL in the info
structure if EXP references a unterminated array. */
c_strlen_data lendata = { };
tree len = c_strlen (exp, 1, &lendata);
if (len == NULL_TREE && lendata.minlen && lendata.decl)
{
if (size)
{
len = lendata.minlen;
if (lendata.off)
{
/* Constant offsets are already accounted for in LENDATA.MINLEN,
but not in a SSA_NAME + CST expression. */
if (TREE_CODE (lendata.off) == INTEGER_CST)
*exact = true;
else if (TREE_CODE (lendata.off) == PLUS_EXPR
&& TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
{
/* Subtract the offset from the size of the array. */
*exact = false;
tree temp = TREE_OPERAND (lendata.off, 1);
temp = fold_convert (ssizetype, temp);
len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
}
else
*exact = false;
}
else
*exact = true;
*size = len;
}
return lendata.decl;
}
return NULL_TREE;
}
/* Compute the length of a null-terminated character string or wide
character string handling character sizes of 1, 2, and 4 bytes.
TREE_STRING_LENGTH is not the right way because it evaluates to
the size of the character array in bytes (as opposed to characters)
and because it can contain a zero byte in the middle.
ONLY_VALUE should be nonzero if the result is not going to be emitted
into the instruction stream and zero if it is going to be expanded.
E.g. with i++ ? "foo" : "bar", if ONLY_VALUE is nonzero, constant 3
is returned, otherwise NULL, since
len = c_strlen (ARG, 1); if (len) expand_expr (len, ...); would not
evaluate the side-effects.
If ONLY_VALUE is two then we do not emit warnings about out-of-bound
accesses. Note that this implies the result is not going to be emitted
into the instruction stream.
Additional information about the string accessed may be recorded
in DATA. For example, if ARG references an unterminated string,
then the declaration will be stored in the DECL field. If the
length of the unterminated string can be determined, it'll be
stored in the LEN field. Note this length could well be different
than what a C strlen call would return.
ELTSIZE is 1 for normal single byte character strings, and 2 or
4 for wide characer strings. ELTSIZE is by default 1.
The value returned is of type `ssizetype'. */
tree
c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
{
/* If we were not passed a DATA pointer, then get one to a local
structure. That avoids having to check DATA for NULL before
each time we want to use it. */
c_strlen_data local_strlen_data = { };
if (!data)
data = &local_strlen_data;
gcc_checking_assert (eltsize == 1 || eltsize == 2 || eltsize == 4);
tree src = STRIP_NOPS (arg);
if (TREE_CODE (src) == COND_EXPR
&& (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0))))
{
tree len1, len2;
len1 = c_strlen (TREE_OPERAND (src, 1), only_value, data, eltsize);
len2 = c_strlen (TREE_OPERAND (src, 2), only_value, data, eltsize);
if (tree_int_cst_equal (len1, len2))
return len1;
}
if (TREE_CODE (src) == COMPOUND_EXPR
&& (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0))))
return c_strlen (TREE_OPERAND (src, 1), only_value, data, eltsize);
location_t loc = EXPR_LOC_OR_LOC (src, input_location);
/* Offset from the beginning of the string in bytes. */
tree byteoff;
tree memsize;
tree decl;
src = string_constant (src, &byteoff, &memsize, &decl);
if (src == 0)
return NULL_TREE;
/* Determine the size of the string element. */
if (eltsize != tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (src)))))
return NULL_TREE;
/* Set MAXELTS to sizeof (SRC) / sizeof (*SRC) - 1, the maximum possible
length of SRC. Prefer TYPE_SIZE() to TREE_STRING_LENGTH() if possible
in case the latter is less than the size of the array, such as when
SRC refers to a short string literal used to initialize a large array.
In that case, the elements of the array after the terminating NUL are
all NUL. */
HOST_WIDE_INT strelts = TREE_STRING_LENGTH (src);
strelts = strelts / eltsize;
if (!tree_fits_uhwi_p (memsize))
return NULL_TREE;
HOST_WIDE_INT maxelts = tree_to_uhwi (memsize) / eltsize;
/* PTR can point to the byte representation of any string type, including
char* and wchar_t*. */
const char *ptr = TREE_STRING_POINTER (src);
if (byteoff && TREE_CODE (byteoff) != INTEGER_CST)
{
/* The code below works only for single byte character types. */
if (eltsize != 1)
return NULL_TREE;
/* If the string has an internal NUL character followed by any
non-NUL characters (e.g., "foo\0bar"), we can't compute
the offset to the following NUL if we don't know where to
start searching for it. */
unsigned len = string_length (ptr, eltsize, strelts);
/* Return when an embedded null character is found or none at all.
In the latter case, set the DECL/LEN field in the DATA structure
so that callers may examine them. */
if (len + 1 < strelts)
return NULL_TREE;
else if (len >= maxelts)
{
data->decl = decl;
data->off = byteoff;
data->minlen = ssize_int (len);
return NULL_TREE;
}
/* For empty strings the result should be zero. */
if (len == 0)
return ssize_int (0);
/* We don't know the starting offset, but we do know that the string
has no internal zero bytes. If the offset falls within the bounds
of the string subtract the offset from the length of the string,
and return that. Otherwise the length is zero. Take care to
use SAVE_EXPR in case the OFFSET has side-effects. */
tree offsave = TREE_SIDE_EFFECTS (byteoff) ? save_expr (byteoff)
: byteoff;
offsave = fold_convert_loc (loc, sizetype, offsave);
tree condexp = fold_build2_loc (loc, LE_EXPR, boolean_type_node, offsave,
size_int (len));
tree lenexp = fold_build2_loc (loc, MINUS_EXPR, sizetype, size_int (len),
offsave);
lenexp = fold_convert_loc (loc, ssizetype, lenexp);
return fold_build3_loc (loc, COND_EXPR, ssizetype, condexp, lenexp,
build_zero_cst (ssizetype));
}
/* Offset from the beginning of the string in elements. */
HOST_WIDE_INT eltoff;
/* We have a known offset into the string. Start searching there for
a null character if we can represent it as a single HOST_WIDE_INT. */
if (byteoff == 0)
eltoff = 0;
else if (! tree_fits_uhwi_p (byteoff) || tree_to_uhwi (byteoff) % eltsize)
eltoff = -1;
else
eltoff = tree_to_uhwi (byteoff) / eltsize;
/* If the offset is known to be out of bounds, warn, and call strlen at
runtime. */
if (eltoff < 0 || eltoff >= maxelts)
{
/* Suppress multiple warnings for propagated constant strings. */
if (only_value != 2
&& !TREE_NO_WARNING (arg)
&& warning_at (loc, OPT_Warray_bounds,
"offset %qwi outside bounds of constant string",
eltoff))
{
if (decl)
inform (DECL_SOURCE_LOCATION (decl), "%qE declared here", decl);
TREE_NO_WARNING (arg) = 1;
}
return NULL_TREE;
}
/* If eltoff is larger than strelts but less than maxelts the
string length is zero, since the excess memory will be zero. */
if (eltoff > strelts)
return ssize_int (0);
/* Use strlen to search for the first zero byte. Since any strings
constructed with build_string will have nulls appended, we win even
if we get handed something like (char[4])"abcd".
Since ELTOFF is our starting index into the string, no further
calculation is needed. */
unsigned len = string_length (ptr + eltoff * eltsize, eltsize,
strelts - eltoff);
/* Don't know what to return if there was no zero termination.
Ideally this would turn into a gcc_checking_assert over time.
Set DECL/LEN so callers can examine them. */
if (len >= maxelts - eltoff)
{
data->decl = decl;
data->off = byteoff;
data->minlen = ssize_int (len);
return NULL_TREE;
}
return ssize_int (len);
}
/* Return a constant integer corresponding to target reading
GET_MODE_BITSIZE (MODE) bits from string constant STR. If
NULL_TERMINATED_P, reading stops after '\0' character, all further ones
are assumed to be zero, otherwise it reads as many characters
as needed. */
rtx
c_readstr (const char *str, scalar_int_mode mode,
bool null_terminated_p/*=true*/)
{
HOST_WIDE_INT ch;
unsigned int i, j;
HOST_WIDE_INT tmp[MAX_BITSIZE_MODE_ANY_INT / HOST_BITS_PER_WIDE_INT];
gcc_assert (GET_MODE_CLASS (mode) == MODE_INT);
unsigned int len = (GET_MODE_PRECISION (mode) + HOST_BITS_PER_WIDE_INT - 1)
/ HOST_BITS_PER_WIDE_INT;
gcc_assert (len <= MAX_BITSIZE_MODE_ANY_INT / HOST_BITS_PER_WIDE_INT);
for (i = 0; i < len; i++)
tmp[i] = 0;
ch = 1;
for (i = 0; i < GET_MODE_SIZE (mode); i++)
{
j = i;
if (WORDS_BIG_ENDIAN)
j = GET_MODE_SIZE (mode) - i - 1;
if (BYTES_BIG_ENDIAN != WORDS_BIG_ENDIAN
&& GET_MODE_SIZE (mode) >= UNITS_PER_WORD)
j = j + UNITS_PER_WORD - 2 * (j % UNITS_PER_WORD) - 1;
j *= BITS_PER_UNIT;
if (ch || !null_terminated_p)
ch = (unsigned char) str[i];
tmp[j / HOST_BITS_PER_WIDE_INT] |= ch << (j % HOST_BITS_PER_WIDE_INT);
}
wide_int c = wide_int::from_array (tmp, len, GET_MODE_PRECISION (mode));
return immed_wide_int_const (c, mode);
}
/* Cast a target constant CST to target CHAR and if that value fits into
host char type, return zero and put that value into variable pointed to by
P. */
static int
target_char_cast (tree cst, char *p)
{
unsigned HOST_WIDE_INT val, hostval;
if (TREE_CODE (cst) != INTEGER_CST
|| CHAR_TYPE_SIZE > HOST_BITS_PER_WIDE_INT)
return 1;
/* Do not care if it fits or not right here. */
val = TREE_INT_CST_LOW (cst);
if (CHAR_TYPE_SIZE < HOST_BITS_PER_WIDE_INT)
val &= (HOST_WIDE_INT_1U << CHAR_TYPE_SIZE) - 1;
hostval = val;
if (HOST_BITS_PER_CHAR < HOST_BITS_PER_WIDE_INT)
hostval &= (HOST_WIDE_INT_1U << HOST_BITS_PER_CHAR) - 1;
if (val != hostval)
return 1;
*p = hostval;
return 0;
}
/* Similar to save_expr, but assumes that arbitrary code is not executed
in between the multiple evaluations. In particular, we assume that a
non-addressable local variable will not be modified. */
static tree
builtin_save_expr (tree exp)
{
if (TREE_CODE (exp) == SSA_NAME
|| (TREE_ADDRESSABLE (exp) == 0
&& (TREE_CODE (exp) == PARM_DECL
|| (VAR_P (exp) && !TREE_STATIC (exp)))))
return exp;
return save_expr (exp);
}
/* Given TEM, a pointer to a stack frame, follow the dynamic chain COUNT
times to get the address of either a higher stack frame, or a return
address located within it (depending on FNDECL_CODE). */
static rtx
expand_builtin_return_addr (enum built_in_function fndecl_code, int count)
{
int i;
rtx tem = INITIAL_FRAME_ADDRESS_RTX;
if (tem == NULL_RTX)
{
/* For a zero count with __builtin_return_address, we don't care what
frame address we return, because target-specific definitions will
override us. Therefore frame pointer elimination is OK, and using
the soft frame pointer is OK.
For a nonzero count, or a zero count with __builtin_frame_address,
we require a stable offset from the current frame pointer to the
previous one, so we must use the hard frame pointer, and
we must disable frame pointer elimination. */
if (count == 0 && fndecl_code == BUILT_IN_RETURN_ADDRESS)
tem = frame_pointer_rtx;
else
{
tem = hard_frame_pointer_rtx;
/* Tell reload not to eliminate the frame pointer. */
crtl->accesses_prior_frames = 1;
}
}
if (count > 0)
SETUP_FRAME_ADDRESSES ();
/* On the SPARC, the return address is not in the frame, it is in a
register. There is no way to access it off of the current frame
pointer, but it can be accessed off the previous frame pointer by
reading the value from the register window save area. */
if (RETURN_ADDR_IN_PREVIOUS_FRAME && fndecl_code == BUILT_IN_RETURN_ADDRESS)
count--;
/* Scan back COUNT frames to the specified frame. */
for (i = 0; i < count; i++)
{
/* Assume the dynamic chain pointer is in the word that the
frame address points to, unless otherwise specified. */
tem = DYNAMIC_CHAIN_ADDRESS (tem);
tem = memory_address (Pmode, tem);
tem = gen_frame_mem (Pmode, tem);
tem = copy_to_reg (tem);
}
/* For __builtin_frame_address, return what we've got. But, on
the SPARC for example, we may have to add a bias. */
if (fndecl_code == BUILT_IN_FRAME_ADDRESS)
return FRAME_ADDR_RTX (tem);
/* For __builtin_return_address, get the return address from that frame. */
#ifdef RETURN_ADDR_RTX
tem = RETURN_ADDR_RTX (count, tem);
#else
tem = memory_address (Pmode,
plus_constant (Pmode, tem, GET_MODE_SIZE (Pmode)));
tem = gen_frame_mem (Pmode, tem);
#endif
return tem;
}
/* Alias set used for setjmp buffer. */
static alias_set_type setjmp_alias_set = -1;
/* Construct the leading half of a __builtin_setjmp call. Control will
return to RECEIVER_LABEL. This is also called directly by the SJLJ
exception handling code. */
void
expand_builtin_setjmp_setup (rtx buf_addr, rtx receiver_label)
{
machine_mode sa_mode = STACK_SAVEAREA_MODE (SAVE_NONLOCAL);
rtx stack_save;
rtx mem;
if (setjmp_alias_set == -1)
setjmp_alias_set = new_alias_set ();
buf_addr = convert_memory_address (Pmode, buf_addr);
buf_addr = force_reg (Pmode, force_operand (buf_addr, NULL_RTX));
/* We store the frame pointer and the address of receiver_label in
the buffer and use the rest of it for the stack save area, which
is machine-dependent. */
mem = gen_rtx_MEM (Pmode, buf_addr);
set_mem_alias_set (mem, setjmp_alias_set);
emit_move_insn (mem, hard_frame_pointer_rtx);
mem = gen_rtx_MEM (Pmode, plus_constant (Pmode, buf_addr,
GET_MODE_SIZE (Pmode))),
set_mem_alias_set (mem, setjmp_alias_set);
emit_move_insn (validize_mem (mem),
force_reg (Pmode, gen_rtx_LABEL_REF (Pmode, receiver_label)));
stack_save = gen_rtx_MEM (sa_mode,
plus_constant (Pmode, buf_addr,
2 * GET_MODE_SIZE (Pmode)));
set_mem_alias_set (stack_save, setjmp_alias_set);
emit_stack_save (SAVE_NONLOCAL, &stack_save);
/* If there is further processing to do, do it. */
if (targetm.have_builtin_setjmp_setup ())
emit_insn (targetm.gen_builtin_setjmp_setup (buf_addr));
/* We have a nonlocal label. */
cfun->has_nonlocal_label = 1;
}
/* Construct the trailing part of a __builtin_setjmp call. This is
also called directly by the SJLJ exception handling code.
If RECEIVER_LABEL is NULL, instead contruct a nonlocal goto handler. */
void
expand_builtin_setjmp_receiver (rtx receiver_label)
{
rtx chain;
/* Mark the FP as used when we get here, so we have to make sure it's
marked as used by this function. */
emit_use (hard_frame_pointer_rtx);
/* Mark the static chain as clobbered here so life information
doesn't get messed up for it. */
chain = rtx_for_static_chain (current_function_decl, true);
if (chain && REG_P (chain))
emit_clobber (chain);
if (!HARD_FRAME_POINTER_IS_ARG_POINTER && fixed_regs[ARG_POINTER_REGNUM])
{
/* If the argument pointer can be eliminated in favor of the
frame pointer, we don't need to restore it. We assume here
that if such an elimination is present, it can always be used.
This is the case on all known machines; if we don't make this
assumption, we do unnecessary saving on many machines. */
size_t i;
static const struct elims {const int from, to;} elim_regs[] = ELIMINABLE_REGS;
for (i = 0; i < ARRAY_SIZE (elim_regs); i++)
if (elim_regs[i].from == ARG_POINTER_REGNUM
&& elim_regs[i].to == HARD_FRAME_POINTER_REGNUM)
break;
if (i == ARRAY_SIZE (elim_regs))
{
/* Now restore our arg pointer from the address at which it
was saved in our stack frame. */
emit_move_insn (crtl->args.internal_arg_pointer,
copy_to_reg (get_arg_pointer_save_area ()));
}
}
if (receiver_label != NULL && targetm.have_builtin_setjmp_receiver ())
emit_insn (targetm.gen_builtin_setjmp_receiver (receiver_label));
else if (targetm.have_nonlocal_goto_receiver ())
emit_insn (targetm.gen_nonlocal_goto_receiver ());
else
{ /* Nothing */ }
/* We must not allow the code we just generated to be reordered by
scheduling. Specifically, the update of the frame pointer must
happen immediately, not later. */
emit_insn (gen_blockage ());
}
/* __builtin_longjmp is passed a pointer to an array of five words (not
all will be used on all machines). It operates similarly to the C
library function of the same name, but is more efficient. Much of
the code below is copied from the handling of non-local gotos. */
static void
expand_builtin_longjmp (rtx buf_addr, rtx value)
{
rtx fp, lab, stack;
rtx_insn *insn, *last;
machine_mode sa_mode = STACK_SAVEAREA_MODE (SAVE_NONLOCAL);
/* DRAP is needed for stack realign if longjmp is expanded to current
function */
if (SUPPORTS_STACK_ALIGNMENT)
crtl->need_drap = true;
if (setjmp_alias_set == -1)
setjmp_alias_set = new_alias_set ();
buf_addr = convert_memory_address (Pmode, buf_addr);
buf_addr = force_reg (Pmode, buf_addr);
/* We require that the user must pass a second argument of 1, because
that is what builtin_setjmp will return. */
gcc_assert (value == const1_rtx);
last = get_last_insn ();
if (targetm.have_builtin_longjmp ())
emit_insn (targetm.gen_builtin_longjmp (buf_addr));
else
{
fp = gen_rtx_MEM (Pmode, buf_addr);
lab = gen_rtx_MEM (Pmode, plus_constant (Pmode, buf_addr,
GET_MODE_SIZE (Pmode)));
stack = gen_rtx_MEM (sa_mode, plus_constant (Pmode, buf_addr,
2 * GET_MODE_SIZE (Pmode)));
set_mem_alias_set (fp, setjmp_alias_set);
set_mem_alias_set (lab, setjmp_alias_set);
set_mem_alias_set (stack, setjmp_alias_set);
/* Pick up FP, label, and SP from the block and jump. This code is
from expand_goto in stmt.c; see there for detailed comments. */
if (targetm.have_nonlocal_goto ())
/* We have to pass a value to the nonlocal_goto pattern that will
get copied into the static_chain pointer, but it does not matter
what that value is, because builtin_setjmp does not use it. */
emit_insn (targetm.gen_nonlocal_goto (value, lab, stack, fp));
else
{
emit_clobber (gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode)));
emit_clobber (gen_rtx_MEM (BLKmode, hard_frame_pointer_rtx));
lab = copy_to_reg (lab);
/* Restore the frame pointer and stack pointer. We must use a
temporary since the setjmp buffer may be a local. */
fp = copy_to_reg (fp);
emit_stack_restore (SAVE_NONLOCAL, stack);
/* Ensure the frame pointer move is not optimized. */
emit_insn (gen_blockage ());
emit_clobber (hard_frame_pointer_rtx);
emit_clobber (frame_pointer_rtx);
emit_move_insn (hard_frame_pointer_rtx, fp);
emit_use (hard_frame_pointer_rtx);
emit_use (stack_pointer_rtx);
emit_indirect_jump (lab);
}
}
/* Search backwards and mark the jump insn as a non-local goto.
Note that this precludes the use of __builtin_longjmp to a
__builtin_setjmp target in the same function. However, we've
already cautioned the user that these functions are for
internal exception handling use only. */
for (insn = get_last_insn (); insn; insn = PREV_INSN (insn))
{
gcc_assert (insn != last);
if (JUMP_P (insn))
{
add_reg_note (insn, REG_NON_LOCAL_GOTO, const0_rtx);
break;
}
else if (CALL_P (insn))
break;
}
}
static inline bool
more_const_call_expr_args_p (const const_call_expr_arg_iterator *iter)
{
return (iter->i < iter->n);
}
/* This function validates the types of a function call argument list
against a specified list of tree_codes. If the last specifier is a 0,
that represents an ellipsis, otherwise the last specifier must be a
VOID_TYPE. */
static bool
validate_arglist (const_tree callexpr, ...)
{
enum tree_code code;
bool res = 0;
va_list ap;
const_call_expr_arg_iterator iter;
const_tree arg;
va_start (ap, callexpr);
init_const_call_expr_arg_iterator (callexpr, &iter);
/* Get a bitmap of pointer argument numbers declared attribute nonnull. */
tree fn = CALL_EXPR_FN (callexpr);
bitmap argmap = get_nonnull_args (TREE_TYPE (TREE_TYPE (fn)));
for (unsigned argno = 1; ; ++argno)
{
code = (enum tree_code) va_arg (ap, int);
switch (code)
{
case 0:
/* This signifies an ellipses, any further arguments are all ok. */
res = true;
goto end;
case VOID_TYPE:
/* This signifies an endlink, if no arguments remain, return
true, otherwise return false. */
res = !more_const_call_expr_args_p (&iter);
goto end;
case POINTER_TYPE:
/* The actual argument must be nonnull when either the whole
called function has been declared nonnull, or when the formal
argument corresponding to the actual argument has been. */
if (argmap
&& (bitmap_empty_p (argmap) || bitmap_bit_p (argmap, argno)))
{
arg = next_const_call_expr_arg (&iter);
if (!validate_arg (arg, code) || integer_zerop (arg))
goto end;
break;
}
/* FALLTHRU */
default:
/* If no parameters remain or the parameter's code does not
match the specified code, return false. Otherwise continue
checking any remaining arguments. */
arg = next_const_call_expr_arg (&iter);
if (!validate_arg (arg, code))
goto end;
break;
}
}
/* We need gotos here since we can only have one VA_CLOSE in a
function. */
end: ;
va_end (ap);
BITMAP_FREE (argmap);
return res;
}
/* Expand a call to __builtin_nonlocal_goto. We're passed the target label
and the address of the save area. */
static rtx
expand_builtin_nonlocal_goto (tree exp)
{
tree t_label, t_save_area;
rtx r_label, r_save_area, r_fp, r_sp;
rtx_insn *insn;
if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
return NULL_RTX;
t_label = CALL_EXPR_ARG (exp, 0);
t_save_area = CALL_EXPR_ARG (exp, 1);
r_label = expand_normal (t_label);
r_label = convert_memory_address (Pmode, r_label);
r_save_area = expand_normal (t_save_area);
r_save_area = convert_memory_address (Pmode, r_save_area);
/* Copy the address of the save location to a register just in case it was
based on the frame pointer. */
r_save_area = copy_to_reg (r_save_area);
r_fp = gen_rtx_MEM (Pmode, r_save_area);
r_sp = gen_rtx_MEM (STACK_SAVEAREA_MODE (SAVE_NONLOCAL),
plus_constant (Pmode, r_save_area,
GET_MODE_SIZE (Pmode)));
crtl->has_nonlocal_goto = 1;
/* ??? We no longer need to pass the static chain value, afaik. */
if (targetm.have_nonlocal_goto ())
emit_insn (targetm.gen_nonlocal_goto (const0_rtx, r_label, r_sp, r_fp));
else
{
emit_clobber (gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode)));
emit_clobber (gen_rtx_MEM (BLKmode, hard_frame_pointer_rtx));
r_label = copy_to_reg (r_label);
/* Restore the frame pointer and stack pointer. We must use a
temporary since the setjmp buffer may be a local. */
r_fp = copy_to_reg (r_fp);
emit_stack_restore (SAVE_NONLOCAL, r_sp);
/* Ensure the frame pointer move is not optimized. */
emit_insn (gen_blockage ());
emit_clobber (hard_frame_pointer_rtx);
emit_clobber (frame_pointer_rtx);
emit_move_insn (hard_frame_pointer_rtx, r_fp);
/* USE of hard_frame_pointer_rtx added for consistency;
not clear if really needed. */
emit_use (hard_frame_pointer_rtx);
emit_use (stack_pointer_rtx);
/* If the architecture is using a GP register, we must
conservatively assume that the target function makes use of it.
The prologue of functions with nonlocal gotos must therefore
initialize the GP register to the appropriate value, and we
must then make sure that this value is live at the point
of the jump. (Note that this doesn't necessarily apply
to targets with a nonlocal_goto pattern; they are free
to implement it in their own way. Note also that this is
a no-op if the GP register is a global invariant.) */
unsigned regnum = PIC_OFFSET_TABLE_REGNUM;
if (regnum != INVALID_REGNUM && fixed_regs[regnum])
emit_use (pic_offset_table_rtx);
emit_indirect_jump (r_label);
}
/* Search backwards to the jump insn and mark it as a
non-local goto. */
for (insn = get_last_insn (); insn; insn = PREV_INSN (insn))
{
if (JUMP_P (insn))
{
add_reg_note (insn, REG_NON_LOCAL_GOTO, const0_rtx);
break;
}
else if (CALL_P (insn))
break;
}
return const0_rtx;
}
/* __builtin_update_setjmp_buf is passed a pointer to an array of five words
(not all will be used on all machines) that was passed to __builtin_setjmp.
It updates the stack pointer in that block to the current value. This is
also called directly by the SJLJ exception handling code. */
void
expand_builtin_update_setjmp_buf (rtx buf_addr)
{
machine_mode sa_mode = STACK_SAVEAREA_MODE (SAVE_NONLOCAL);
buf_addr = convert_memory_address (Pmode, buf_addr);
rtx stack_save
= gen_rtx_MEM (sa_mode,
memory_address
(sa_mode,
plus_constant (Pmode, buf_addr,
2 * GET_MODE_SIZE (Pmode))));
emit_stack_save (SAVE_NONLOCAL, &stack_save);
}
/* Expand a call to __builtin_prefetch. For a target that does not support
data prefetch, evaluate the memory address argument in case it has side
effects. */
static void
expand_builtin_prefetch (tree exp)
{
tree arg0, arg1, arg2;
int nargs;
rtx op0, op1, op2;
if (!validate_arglist (exp, POINTER_TYPE, 0))
return;
arg0 = CALL_EXPR_ARG (exp, 0);
/* Arguments 1 and 2 are optional; argument 1 (read/write) defaults to
zero (read) and argument 2 (locality) defaults to 3 (high degree of
locality). */
nargs = call_expr_nargs (exp);
if (nargs > 1)
arg1 = CALL_EXPR_ARG (exp, 1);
else
arg1 = integer_zero_node;
if (nargs > 2)
arg2 = CALL_EXPR_ARG (exp, 2);
else
arg2 = integer_three_node;
/* Argument 0 is an address. */
op0 = expand_expr (arg0, NULL_RTX, Pmode, EXPAND_NORMAL);
/* Argument 1 (read/write flag) must be a compile-time constant int. */
if (TREE_CODE (arg1) != INTEGER_CST)
{
error ("second argument to %<__builtin_prefetch%> must be a constant");
arg1 = integer_zero_node;
}
op1 = expand_normal (arg1);
/* Argument 1 must be either zero or one. */
if (INTVAL (op1) != 0 && INTVAL (op1) != 1)
{
warning (0, "invalid second argument to %<__builtin_prefetch%>;"
" using zero");
op1 = const0_rtx;
}
/* Argument 2 (locality) must be a compile-time constant int. */
if (TREE_CODE (arg2) != INTEGER_CST)
{
error ("third argument to %<__builtin_prefetch%> must be a constant");
arg2 = integer_zero_node;
}
op2 = expand_normal (arg2);
/* Argument 2 must be 0, 1, 2, or 3. */
if (INTVAL (op2) < 0 || INTVAL (op2) > 3)
{
warning (0, "invalid third argument to %<__builtin_prefetch%>; using zero");
op2 = const0_rtx;
}
if (targetm.have_prefetch ())
{
class expand_operand ops[3];
create_address_operand (&ops[0], op0);
create_integer_operand (&ops[1], INTVAL (op1));
create_integer_operand (&ops[2], INTVAL (op2));
if (maybe_expand_insn (targetm.code_for_prefetch, 3, ops))
return;
}
/* Don't do anything with direct references to volatile memory, but
generate code to handle other side effects. */
if (!MEM_P (op0) && side_effects_p (op0))
emit_insn (op0);
}
/* Get a MEM rtx for expression EXP which is the address of an operand
to be used in a string instruction (cmpstrsi, cpymemsi, ..). LEN is
the maximum length of the block of memory that might be accessed or
NULL if unknown. */
static rtx
get_memory_rtx (tree exp, tree len)
{
tree orig_exp = exp;
rtx addr, mem;
/* When EXP is not resolved SAVE_EXPR, MEM_ATTRS can be still derived
from its expression, for expr->a.b only <variable>.a.b is recorded. */
if (TREE_CODE (exp) == SAVE_EXPR && !SAVE_EXPR_RESOLVED_P (exp))
exp = TREE_OPERAND (exp, 0);
addr = expand_expr (orig_exp, NULL_RTX, ptr_mode, EXPAND_NORMAL);
mem = gen_rtx_MEM (BLKmode, memory_address (BLKmode, addr));
/* Get an expression we can use to find the attributes to assign to MEM.
First remove any nops. */
while (CONVERT_EXPR_P (exp)
&& POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (exp, 0))))
exp = TREE_OPERAND (exp, 0);
/* Build a MEM_REF representing the whole accessed area as a byte blob,
(as builtin stringops may alias with anything). */
exp = fold_build2 (MEM_REF,
build_array_type (char_type_node,
build_range_type (sizetype,
size_one_node, len)),
exp, build_int_cst (ptr_type_node, 0));
/* If the MEM_REF has no acceptable address, try to get the base object
from the original address we got, and build an all-aliasing
unknown-sized access to that one. */
if (is_gimple_mem_ref_addr (TREE_OPERAND (exp, 0)))
set_mem_attributes (mem, exp, 0);
else if (TREE_CODE (TREE_OPERAND (exp, 0)) == ADDR_EXPR
&& (exp = get_base_address (TREE_OPERAND (TREE_OPERAND (exp, 0),
0))))
{
exp = build_fold_addr_expr (exp);
exp = fold_build2 (MEM_REF,
build_array_type (char_type_node,
build_range_type (sizetype,
size_zero_node,
NULL)),
exp, build_int_cst (ptr_type_node, 0));
set_mem_attributes (mem, exp, 0);
}
set_mem_alias_set (mem, 0);
return mem;
}
/* Built-in functions to perform an untyped call and return. */
#define apply_args_mode \
(this_target_builtins->x_apply_args_mode)
#define apply_result_mode \
(this_target_builtins->x_apply_result_mode)
/* Return the size required for the block returned by __builtin_apply_args,
and initialize apply_args_mode. */
static int
apply_args_size (void)
{
static int size = -1;
int align;
unsigned int regno;
/* The values computed by this function never change. */
if (size < 0)
{
/* The first value is the incoming arg-pointer. */
size = GET_MODE_SIZE (Pmode);
/* The second value is the structure value address unless this is
passed as an "invisible" first argument. */
if (targetm.calls.struct_value_rtx (cfun ? TREE_TYPE (cfun->decl) : 0, 0))
size += GET_MODE_SIZE (Pmode);
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if (FUNCTION_ARG_REGNO_P (regno))
{
fixed_size_mode mode = targetm.calls.get_raw_arg_mode (regno);
gcc_assert (mode != VOIDmode);
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
size += GET_MODE_SIZE (mode);
apply_args_mode[regno] = mode;
}
else
{
apply_args_mode[regno] = as_a <fixed_size_mode> (VOIDmode);
}
}
return size;
}
/* Return the size required for the block returned by __builtin_apply,
and initialize apply_result_mode. */
static int
apply_result_size (void)
{
static int size = -1;
int align, regno;
/* The values computed by this function never change. */
if (size < 0)
{
size = 0;
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if (targetm.calls.function_value_regno_p (regno))
{
fixed_size_mode mode = targetm.calls.get_raw_result_mode (regno);
gcc_assert (mode != VOIDmode);
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
size += GET_MODE_SIZE (mode);
apply_result_mode[regno] = mode;
}
else
apply_result_mode[regno] = as_a <fixed_size_mode> (VOIDmode);
/* Allow targets that use untyped_call and untyped_return to override
the size so that machine-specific information can be stored here. */
#ifdef APPLY_RESULT_SIZE
size = APPLY_RESULT_SIZE;
#endif
}
return size;
}
/* Create a vector describing the result block RESULT. If SAVEP is true,
the result block is used to save the values; otherwise it is used to
restore the values. */
static rtx
result_vector (int savep, rtx result)
{
int regno, size, align, nelts;
fixed_size_mode mode;
rtx reg, mem;
rtx *savevec = XALLOCAVEC (rtx, FIRST_PSEUDO_REGISTER);
size = nelts = 0;
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if ((mode = apply_result_mode[regno]) != VOIDmode)
{
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
reg = gen_rtx_REG (mode, savep ? regno : INCOMING_REGNO (regno));
mem = adjust_address (result, mode, size);
savevec[nelts++] = (savep
? gen_rtx_SET (mem, reg)
: gen_rtx_SET (reg, mem));
size += GET_MODE_SIZE (mode);
}
return gen_rtx_PARALLEL (VOIDmode, gen_rtvec_v (nelts, savevec));
}
/* Save the state required to perform an untyped call with the same
arguments as were passed to the current function. */
static rtx
expand_builtin_apply_args_1 (void)
{
rtx registers, tem;
int size, align, regno;
fixed_size_mode mode;
rtx struct_incoming_value = targetm.calls.struct_value_rtx (cfun ? TREE_TYPE (cfun->decl) : 0, 1);
/* Create a block where the arg-pointer, structure value address,
and argument registers can be saved. */
registers = assign_stack_local (BLKmode, apply_args_size (), -1);
/* Walk past the arg-pointer and structure value address. */
size = GET_MODE_SIZE (Pmode);
if (targetm.calls.struct_value_rtx (cfun ? TREE_TYPE (cfun->decl) : 0, 0))
size += GET_MODE_SIZE (Pmode);
/* Save each register used in calling a function to the block. */
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if ((mode = apply_args_mode[regno]) != VOIDmode)
{
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
tem = gen_rtx_REG (mode, INCOMING_REGNO (regno));
emit_move_insn (adjust_address (registers, mode, size), tem);
size += GET_MODE_SIZE (mode);
}
/* Save the arg pointer to the block. */
tem = copy_to_reg (crtl->args.internal_arg_pointer);
/* We need the pointer as the caller actually passed them to us, not
as we might have pretended they were passed. Make sure it's a valid
operand, as emit_move_insn isn't expected to handle a PLUS. */
if (STACK_GROWS_DOWNWARD)
tem
= force_operand (plus_constant (Pmode, tem,
crtl->args.pretend_args_size),
NULL_RTX);
emit_move_insn (adjust_address (registers, Pmode, 0), tem);
size = GET_MODE_SIZE (Pmode);
/* Save the structure value address unless this is passed as an
"invisible" first argument. */
if (struct_incoming_value)
emit_move_insn (adjust_address (registers, Pmode, size),
copy_to_reg (struct_incoming_value));
/* Return the address of the block. */
return copy_addr_to_reg (XEXP (registers, 0));
}
/* __builtin_apply_args returns block of memory allocated on
the stack into which is stored the arg pointer, structure
value address, static chain, and all the registers that might
possibly be used in performing a function call. The code is
moved to the start of the function so the incoming values are
saved. */
static rtx
expand_builtin_apply_args (void)
{
/* Don't do __builtin_apply_args more than once in a function.
Save the result of the first call and reuse it. */
if (apply_args_value != 0)
return apply_args_value;
{
/* When this function is called, it means that registers must be
saved on entry to this function. So we migrate the
call to the first insn of this function. */
rtx temp;
start_sequence ();
temp = expand_builtin_apply_args_1 ();
rtx_insn *seq = get_insns ();
end_sequence ();
apply_args_value = temp;
/* Put the insns after the NOTE that starts the function.
If this is inside a start_sequence, make the outer-level insn
chain current, so the code is placed at the start of the
function. If internal_arg_pointer is a non-virtual pseudo,
it needs to be placed after the function that initializes
that pseudo. */
push_topmost_sequence ();
if (REG_P (crtl->args.internal_arg_pointer)
&& REGNO (crtl->args.internal_arg_pointer) > LAST_VIRTUAL_REGISTER)
emit_insn_before (seq, parm_birth_insn);
else
emit_insn_before (seq, NEXT_INSN (entry_of_function ()));
pop_topmost_sequence ();
return temp;
}
}
/* Perform an untyped call and save the state required to perform an
untyped return of whatever value was returned by the given function. */
static rtx
expand_builtin_apply (rtx function, rtx arguments, rtx argsize)
{
int size, align, regno;
fixed_size_mode mode;
rtx incoming_args, result, reg, dest, src;
rtx_call_insn *call_insn;
rtx old_stack_level = 0;
rtx call_fusage = 0;
rtx struct_value = targetm.calls.struct_value_rtx (cfun ? TREE_TYPE (cfun->decl) : 0, 0);
arguments = convert_memory_address (Pmode, arguments);
/* Create a block where the return registers can be saved. */
result = assign_stack_local (BLKmode, apply_result_size (), -1);
/* Fetch the arg pointer from the ARGUMENTS block. */
incoming_args = gen_reg_rtx (Pmode);
emit_move_insn (incoming_args, gen_rtx_MEM (Pmode, arguments));
if (!STACK_GROWS_DOWNWARD)
incoming_args = expand_simple_binop (Pmode, MINUS, incoming_args, argsize,
incoming_args, 0, OPTAB_LIB_WIDEN);
/* Push a new argument block and copy the arguments. Do not allow
the (potential) memcpy call below to interfere with our stack
manipulations. */
do_pending_stack_adjust ();
NO_DEFER_POP;
/* Save the stack with nonlocal if available. */
if (targetm.have_save_stack_nonlocal ())
emit_stack_save (SAVE_NONLOCAL, &old_stack_level);
else
emit_stack_save (SAVE_BLOCK, &old_stack_level);
/* Allocate a block of memory onto the stack and copy the memory
arguments to the outgoing arguments address. We can pass TRUE
as the 4th argument because we just saved the stack pointer
and will restore it right after the call. */
allocate_dynamic_stack_space (argsize, 0, BIGGEST_ALIGNMENT, -1, true);
/* Set DRAP flag to true, even though allocate_dynamic_stack_space
may have already set current_function_calls_alloca to true.
current_function_calls_alloca won't be set if argsize is zero,
so we have to guarantee need_drap is true here. */
if (SUPPORTS_STACK_ALIGNMENT)
crtl->need_drap = true;
dest = virtual_outgoing_args_rtx;
if (!STACK_GROWS_DOWNWARD)
{
if (CONST_INT_P (argsize))
dest = plus_constant (Pmode, dest, -INTVAL (argsize));
else
dest = gen_rtx_PLUS (Pmode, dest, negate_rtx (Pmode, argsize));
}
dest = gen_rtx_MEM (BLKmode, dest);
set_mem_align (dest, PARM_BOUNDARY);
src = gen_rtx_MEM (BLKmode, incoming_args);
set_mem_align (src, PARM_BOUNDARY);
emit_block_move (dest, src, argsize, BLOCK_OP_NORMAL);
/* Refer to the argument block. */
apply_args_size ();
arguments = gen_rtx_MEM (BLKmode, arguments);
set_mem_align (arguments, PARM_BOUNDARY);
/* Walk past the arg-pointer and structure value address. */
size = GET_MODE_SIZE (Pmode);
if (struct_value)
size += GET_MODE_SIZE (Pmode);
/* Restore each of the registers previously saved. Make USE insns
for each of these registers for use in making the call. */
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if ((mode = apply_args_mode[regno]) != VOIDmode)
{
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
reg = gen_rtx_REG (mode, regno);
emit_move_insn (reg, adjust_address (arguments, mode, size));
use_reg (&call_fusage, reg);
size += GET_MODE_SIZE (mode);
}
/* Restore the structure value address unless this is passed as an
"invisible" first argument. */
size = GET_MODE_SIZE (Pmode);
if (struct_value)
{
rtx value = gen_reg_rtx (Pmode);
emit_move_insn (value, adjust_address (arguments, Pmode, size));
emit_move_insn (struct_value, value);
if (REG_P (struct_value))
use_reg (&call_fusage, struct_value);
}
/* All arguments and registers used for the call are set up by now! */
function = prepare_call_address (NULL, function, NULL, &call_fusage, 0, 0);
/* Ensure address is valid. SYMBOL_REF is already valid, so no need,
and we don't want to load it into a register as an optimization,
because prepare_call_address already did it if it should be done. */
if (GET_CODE (function) != SYMBOL_REF)
function = memory_address (FUNCTION_MODE, function);
/* Generate the actual call instruction and save the return value. */
if (targetm.have_untyped_call ())
{
rtx mem = gen_rtx_MEM (FUNCTION_MODE, function);
rtx_insn *seq = targetm.gen_untyped_call (mem, result,
result_vector (1, result));
for (rtx_insn *insn = seq; insn; insn = NEXT_INSN (insn))
if (CALL_P (insn))
add_reg_note (insn, REG_UNTYPED_CALL, NULL_RTX);
emit_insn (seq);
}
else if (targetm.have_call_value ())
{
rtx valreg = 0;
/* Locate the unique return register. It is not possible to
express a call that sets more than one return register using
call_value; use untyped_call for that. In fact, untyped_call
only needs to save the return registers in the given block. */
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if ((mode = apply_result_mode[regno]) != VOIDmode)
{
gcc_assert (!valreg); /* have_untyped_call required. */
valreg = gen_rtx_REG (mode, regno);
}
emit_insn (targetm.gen_call_value (valreg,
gen_rtx_MEM (FUNCTION_MODE, function),
const0_rtx, NULL_RTX, const0_rtx));
emit_move_insn (adjust_address (result, GET_MODE (valreg), 0), valreg);
}
else
gcc_unreachable ();
/* Find the CALL insn we just emitted, and attach the register usage
information. */
call_insn = last_call_insn ();
add_function_usage_to (call_insn, call_fusage);
/* Restore the stack. */
if (targetm.have_save_stack_nonlocal ())
emit_stack_restore (SAVE_NONLOCAL, old_stack_level);
else
emit_stack_restore (SAVE_BLOCK, old_stack_level);
fixup_args_size_notes (call_insn, get_last_insn (), 0);
OK_DEFER_POP;
/* Return the address of the result block. */
result = copy_addr_to_reg (XEXP (result, 0));
return convert_memory_address (ptr_mode, result);
}
/* Perform an untyped return. */
static void
expand_builtin_return (rtx result)
{
int size, align, regno;
fixed_size_mode mode;
rtx reg;
rtx_insn *call_fusage = 0;
result = convert_memory_address (Pmode, result);
apply_result_size ();
result = gen_rtx_MEM (BLKmode, result);
if (targetm.have_untyped_return ())
{
rtx vector = result_vector (0, result);
emit_jump_insn (targetm.gen_untyped_return (result, vector));
emit_barrier ();
return;
}
/* Restore the return value and note that each value is used. */
size = 0;
for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++)
if ((mode = apply_result_mode[regno]) != VOIDmode)
{
align = GET_MODE_ALIGNMENT (mode) / BITS_PER_UNIT;
if (size % align != 0)
size = CEIL (size, align) * align;
reg = gen_rtx_REG (mode, INCOMING_REGNO (regno));
emit_move_insn (reg, adjust_address (result, mode, size));
push_to_sequence (call_fusage);
emit_use (reg);
call_fusage = get_insns ();
end_sequence ();
size += GET_MODE_SIZE (mode);
}
/* Put the USE insns before the return. */
emit_insn (call_fusage);
/* Return whatever values was restored by jumping directly to the end
of the function. */
expand_naked_return ();
}
/* Used by expand_builtin_classify_type and fold_builtin_classify_type. */
static enum type_class
type_to_class (tree type)
{
switch (TREE_CODE (type))
{
case VOID_TYPE: return void_type_class;
case INTEGER_TYPE: return integer_type_class;
case ENUMERAL_TYPE: return enumeral_type_class;
case BOOLEAN_TYPE: return boolean_type_class;
case POINTER_TYPE: return pointer_type_class;
case REFERENCE_TYPE: return reference_type_class;
case OFFSET_TYPE: return offset_type_class;
case REAL_TYPE: return real_type_class;
case COMPLEX_TYPE: return complex_type_class;
case FUNCTION_TYPE: return function_type_class;
case METHOD_TYPE: return method_type_class;
case RECORD_TYPE: return record_type_class;
case UNION_TYPE:
case QUAL_UNION_TYPE: return union_type_class;
case ARRAY_TYPE: return (TYPE_STRING_FLAG (type)
? string_type_class : array_type_class);
case LANG_TYPE: return lang_type_class;
case OPAQUE_TYPE: return opaque_type_class;
default: return no_type_class;
}
}
/* Expand a call EXP to __builtin_classify_type. */
static rtx
expand_builtin_classify_type (tree exp)
{
if (call_expr_nargs (exp))
return GEN_INT (type_to_class (TREE_TYPE (CALL_EXPR_ARG (exp, 0))));
return GEN_INT (no_type_class);
}
/* This helper macro, meant to be used in mathfn_built_in below, determines
which among a set of builtin math functions is appropriate for a given type
mode. The `F' (float) and `L' (long double) are automatically generated
from the 'double' case. If a function supports the _Float<N> and _Float<N>X
types, there are additional types that are considered with 'F32', 'F64',
'F128', etc. suffixes. */
#define CASE_MATHFN(MATHFN) \
CASE_CFN_##MATHFN: \
fcode = BUILT_IN_##MATHFN; fcodef = BUILT_IN_##MATHFN##F ; \
fcodel = BUILT_IN_##MATHFN##L ; break;
/* Similar to the above, but also add support for the _Float<N> and _Float<N>X
types. */
#define CASE_MATHFN_FLOATN(MATHFN) \
CASE_CFN_##MATHFN: \
fcode = BUILT_IN_##MATHFN; fcodef = BUILT_IN_##MATHFN##F ; \
fcodel = BUILT_IN_##MATHFN##L ; fcodef16 = BUILT_IN_##MATHFN##F16 ; \
fcodef32 = BUILT_IN_##MATHFN##F32; fcodef64 = BUILT_IN_##MATHFN##F64 ; \
fcodef128 = BUILT_IN_##MATHFN##F128 ; fcodef32x = BUILT_IN_##MATHFN##F32X ; \
fcodef64x = BUILT_IN_##MATHFN##F64X ; fcodef128x = BUILT_IN_##MATHFN##F128X ;\
break;
/* Similar to above, but appends _R after any F/L suffix. */
#define CASE_MATHFN_REENT(MATHFN) \
case CFN_BUILT_IN_##MATHFN##_R: \
case CFN_BUILT_IN_##MATHFN##F_R: \
case CFN_BUILT_IN_##MATHFN##L_R: \
fcode = BUILT_IN_##MATHFN##_R; fcodef = BUILT_IN_##MATHFN##F_R ; \
fcodel = BUILT_IN_##MATHFN##L_R ; break;
/* Return a function equivalent to FN but operating on floating-point
values of type TYPE, or END_BUILTINS if no such function exists.
This is purely an operation on function codes; it does not guarantee
that the target actually has an implementation of the function. */
static built_in_function
mathfn_built_in_2 (tree type, combined_fn fn)
{
tree mtype;
built_in_function fcode, fcodef, fcodel;
built_in_function fcodef16 = END_BUILTINS;
built_in_function fcodef32 = END_BUILTINS;
built_in_function fcodef64 = END_BUILTINS;
built_in_function fcodef128 = END_BUILTINS;
built_in_function fcodef32x = END_BUILTINS;
built_in_function fcodef64x = END_BUILTINS;
built_in_function fcodef128x = END_BUILTINS;
switch (fn)
{
#define SEQ_OF_CASE_MATHFN \
CASE_MATHFN (ACOS) \
CASE_MATHFN (ACOSH) \
CASE_MATHFN (ASIN) \
CASE_MATHFN (ASINH) \
CASE_MATHFN (ATAN) \
CASE_MATHFN (ATAN2) \
CASE_MATHFN (ATANH) \
CASE_MATHFN (CBRT) \
CASE_MATHFN_FLOATN (CEIL) \
CASE_MATHFN (CEXPI) \
CASE_MATHFN_FLOATN (COPYSIGN) \
CASE_MATHFN (COS) \
CASE_MATHFN (COSH) \
CASE_MATHFN (DREM) \
CASE_MATHFN (ERF) \
CASE_MATHFN (ERFC) \
CASE_MATHFN (EXP) \
CASE_MATHFN (EXP10) \
CASE_MATHFN (EXP2) \
CASE_MATHFN (EXPM1) \
CASE_MATHFN (FABS) \
CASE_MATHFN (FDIM) \
CASE_MATHFN_FLOATN (FLOOR) \
CASE_MATHFN_FLOATN (FMA) \
CASE_MATHFN_FLOATN (FMAX) \
CASE_MATHFN_FLOATN (FMIN) \
CASE_MATHFN (FMOD) \
CASE_MATHFN (FREXP) \
CASE_MATHFN (GAMMA) \
CASE_MATHFN_REENT (GAMMA) /* GAMMA_R */ \
CASE_MATHFN (HUGE_VAL) \
CASE_MATHFN (HYPOT) \
CASE_MATHFN (ILOGB) \
CASE_MATHFN (ICEIL) \
CASE_MATHFN (IFLOOR) \
CASE_MATHFN (INF) \
CASE_MATHFN (IRINT) \
CASE_MATHFN (IROUND) \
CASE_MATHFN (ISINF) \
CASE_MATHFN (J0) \
CASE_MATHFN (J1) \
CASE_MATHFN (JN) \
CASE_MATHFN (LCEIL) \
CASE_MATHFN (LDEXP) \
CASE_MATHFN (LFLOOR) \
CASE_MATHFN (LGAMMA) \
CASE_MATHFN_REENT (LGAMMA) /* LGAMMA_R */ \
CASE_MATHFN (LLCEIL) \
CASE_MATHFN (LLFLOOR) \
CASE_MATHFN (LLRINT) \
CASE_MATHFN (LLROUND) \
CASE_MATHFN (LOG) \
CASE_MATHFN (LOG10) \
CASE_MATHFN (LOG1P) \
CASE_MATHFN (LOG2) \
CASE_MATHFN (LOGB) \
CASE_MATHFN (LRINT) \
CASE_MATHFN (LROUND) \
CASE_MATHFN (MODF) \
CASE_MATHFN (NAN) \
CASE_MATHFN (NANS) \
CASE_MATHFN_FLOATN (NEARBYINT) \
CASE_MATHFN (NEXTAFTER) \
CASE_MATHFN (NEXTTOWARD) \
CASE_MATHFN (POW) \
CASE_MATHFN (POWI) \
CASE_MATHFN (POW10) \
CASE_MATHFN (REMAINDER) \
CASE_MATHFN (REMQUO) \
CASE_MATHFN_FLOATN (RINT) \
CASE_MATHFN_FLOATN (ROUND) \
CASE_MATHFN_FLOATN (ROUNDEVEN) \
CASE_MATHFN (SCALB) \
CASE_MATHFN (SCALBLN) \
CASE_MATHFN (SCALBN) \
CASE_MATHFN (SIGNBIT) \
CASE_MATHFN (SIGNIFICAND) \
CASE_MATHFN (SIN) \
CASE_MATHFN (SINCOS) \
CASE_MATHFN (SINH) \
CASE_MATHFN_FLOATN (SQRT) \
CASE_MATHFN (TAN) \
CASE_MATHFN (TANH) \
CASE_MATHFN (TGAMMA) \
CASE_MATHFN_FLOATN (TRUNC) \
CASE_MATHFN (Y0) \
CASE_MATHFN (Y1) \
CASE_MATHFN (YN)
SEQ_OF_CASE_MATHFN
default:
return END_BUILTINS;
}
mtype = TYPE_MAIN_VARIANT (type);
if (mtype == double_type_node)
return fcode;
else if (mtype == float_type_node)
return fcodef;
else if (mtype == long_double_type_node)
return fcodel;
else if (mtype == float16_type_node)
return fcodef16;
else if (mtype == float32_type_node)
return fcodef32;
else if (mtype == float64_type_node)
return fcodef64;
else if (mtype == float128_type_node)
return fcodef128;
else if (mtype == float32x_type_node)
return fcodef32x;
else if (mtype == float64x_type_node)
return fcodef64x;
else if (mtype == float128x_type_node)
return fcodef128x;
else
return END_BUILTINS;
}
#undef CASE_MATHFN
#undef CASE_MATHFN_FLOATN
#undef CASE_MATHFN_REENT
/* Return mathematic function equivalent to FN but operating directly on TYPE,
if available. If IMPLICIT_P is true use the implicit builtin declaration,
otherwise use the explicit declaration. If we can't do the conversion,
return null. */
static tree
mathfn_built_in_1 (tree type, combined_fn fn, bool implicit_p)
{
built_in_function fcode2 = mathfn_built_in_2 (type, fn);
if (fcode2 == END_BUILTINS)
return NULL_TREE;
if (implicit_p && !builtin_decl_implicit_p (fcode2))
return NULL_TREE;
return builtin_decl_explicit (fcode2);
}
/* Like mathfn_built_in_1, but always use the implicit array. */
tree
mathfn_built_in (tree type, combined_fn fn)
{
return mathfn_built_in_1 (type, fn, /*implicit=*/ 1);
}
/* Like mathfn_built_in_1, but take a built_in_function and
always use the implicit array. */
tree
mathfn_built_in (tree type, enum built_in_function fn)
{
return mathfn_built_in_1 (type, as_combined_fn (fn), /*implicit=*/ 1);
}
/* Return the type associated with a built in function, i.e., the one
to be passed to mathfn_built_in to get the type-specific
function. */
tree
mathfn_built_in_type (combined_fn fn)
{
#define CASE_MATHFN(MATHFN) \
case CFN_BUILT_IN_##MATHFN: \
return double_type_node; \
case CFN_BUILT_IN_##MATHFN##F: \
return float_type_node; \
case CFN_BUILT_IN_##MATHFN##L: \
return long_double_type_node;
#define CASE_MATHFN_FLOATN(MATHFN) \
CASE_MATHFN(MATHFN) \
case CFN_BUILT_IN_##MATHFN##F16: \
return float16_type_node; \
case CFN_BUILT_IN_##MATHFN##F32: \
return float32_type_node; \
case CFN_BUILT_IN_##MATHFN##F64: \
return float64_type_node; \
case CFN_BUILT_IN_##MATHFN##F128: \
return float128_type_node; \
case CFN_BUILT_IN_##MATHFN##F32X: \
return float32x_type_node; \
case CFN_BUILT_IN_##MATHFN##F64X: \
return float64x_type_node; \
case CFN_BUILT_IN_##MATHFN##F128X: \
return float128x_type_node;
/* Similar to above, but appends _R after any F/L suffix. */
#define CASE_MATHFN_REENT(MATHFN) \
case CFN_BUILT_IN_##MATHFN##_R: \
return double_type_node; \
case CFN_BUILT_IN_##MATHFN##F_R: \
return float_type_node; \
case CFN_BUILT_IN_##MATHFN##L_R: \
return long_double_type_node;
switch (fn)
{
SEQ_OF_CASE_MATHFN
default:
return NULL_TREE;
}
#undef CASE_MATHFN
#undef CASE_MATHFN_FLOATN
#undef CASE_MATHFN_REENT
#undef SEQ_OF_CASE_MATHFN
}
/* If BUILT_IN_NORMAL function FNDECL has an associated internal function,
return its code, otherwise return IFN_LAST. Note that this function
only tests whether the function is defined in internals.def, not whether
it is actually available on the target. */
internal_fn
associated_internal_fn (tree fndecl)
{
gcc_checking_assert (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL);
tree return_type = TREE_TYPE (TREE_TYPE (fndecl));
switch (DECL_FUNCTION_CODE (fndecl))
{
#define DEF_INTERNAL_FLT_FN(NAME, FLAGS, OPTAB, TYPE) \
CASE_FLT_FN (BUILT_IN_##NAME): return IFN_##NAME;
#define DEF_INTERNAL_FLT_FLOATN_FN(NAME, FLAGS, OPTAB, TYPE) \
CASE_FLT_FN (BUILT_IN_##NAME): return IFN_##NAME; \
CASE_FLT_FN_FLOATN_NX (BUILT_IN_##NAME): return IFN_##NAME;
#define DEF_INTERNAL_INT_FN(NAME, FLAGS, OPTAB, TYPE) \
CASE_INT_FN (BUILT_IN_##NAME): return IFN_##NAME;
#include "internal-fn.def"
CASE_FLT_FN (BUILT_IN_POW10):
return IFN_EXP10;
CASE_FLT_FN (BUILT_IN_DREM):
return IFN_REMAINDER;
CASE_FLT_FN (BUILT_IN_SCALBN):
CASE_FLT_FN (BUILT_IN_SCALBLN):
if (REAL_MODE_FORMAT (TYPE_MODE (return_type))->b == 2)
return IFN_LDEXP;
return IFN_LAST;
default:
return IFN_LAST;
}
}
/* If CALL is a call to a BUILT_IN_NORMAL function that could be replaced
on the current target by a call to an internal function, return the
code of that internal function, otherwise return IFN_LAST. The caller
is responsible for ensuring that any side-effects of the built-in
call are dealt with correctly. E.g. if CALL sets errno, the caller
must decide that the errno result isn't needed or make it available
in some other way. */
internal_fn
replacement_internal_fn (gcall *call)
{
if (gimple_call_builtin_p (call, BUILT_IN_NORMAL))
{
internal_fn ifn = associated_internal_fn (gimple_call_fndecl (call));
if (ifn != IFN_LAST)
{
tree_pair types = direct_internal_fn_types (ifn, call);
optimization_type opt_type = bb_optimization_type (gimple_bb (call));
if (direct_internal_fn_supported_p (ifn, types, opt_type))
return ifn;
}
}
return IFN_LAST;
}
/* Expand a call to the builtin trinary math functions (fma).
Return NULL_RTX if a normal call should be emitted rather than expanding the
function in-line. EXP is the expression that is a call to the builtin
function; if convenient, the result should be placed in TARGET.
SUBTARGET may be used as the target for computing one of EXP's
operands. */
static rtx
expand_builtin_mathfn_ternary (tree exp, rtx target, rtx subtarget)
{
optab builtin_optab;
rtx op0, op1, op2, result;
rtx_insn *insns;
tree fndecl = get_callee_fndecl (exp);
tree arg0, arg1, arg2;
machine_mode mode;
if (!validate_arglist (exp, REAL_TYPE, REAL_TYPE, REAL_TYPE, VOID_TYPE))
return NULL_RTX;
arg0 = CALL_EXPR_ARG (exp, 0);
arg1 = CALL_EXPR_ARG (exp, 1);
arg2 = CALL_EXPR_ARG (exp, 2);
switch (DECL_FUNCTION_CODE (fndecl))
{
CASE_FLT_FN (BUILT_IN_FMA):
CASE_FLT_FN_FLOATN_NX (BUILT_IN_FMA):
builtin_optab = fma_optab; break;
default:
gcc_unreachable ();
}
/* Make a suitable register to place result in. */
mode = TYPE_MODE (TREE_TYPE (exp));
/* Before working hard, check whether the instruction is available. */
if (optab_handler (builtin_optab, mode) == CODE_FOR_nothing)
return NULL_RTX;
result = gen_reg_rtx (mode);
/* Always stabilize the argument list. */
CALL_EXPR_ARG (exp, 0) = arg0 = builtin_save_expr (arg0);
CALL_EXPR_ARG (exp, 1) = arg1 = builtin_save_expr (arg1);
CALL_EXPR_ARG (exp, 2) = arg2 = builtin_save_expr (arg2);
op0 = expand_expr (arg0, subtarget, VOIDmode, EXPAND_NORMAL);
op1 = expand_normal (arg1);
op2 = expand_normal (arg2);
start_sequence ();
/* Compute into RESULT.
Set RESULT to wherever the result comes back. */
result = expand_ternary_op (mode, builtin_optab, op0, op1, op2,
result, 0);
/* If we were unable to expand via the builtin, stop the sequence
(without outputting the insns) and call to the library function
with the stabilized argument list. */
if (result == 0)
{
end_sequence ();
return expand_call (exp, target, target == const0_rtx);
}
/* Output the entire sequence. */
insns = get_insns ();
end_sequence ();
emit_insn (insns);
return result;
}
/* Expand a call to the builtin sin and cos math functions.
Return NULL_RTX if a normal call should be emitted rather than expanding the
function in-line. EXP is the expression that is a call to the builtin
function; if convenient, the result should be placed in TARGET.
SUBTARGET may be used as the target for computing one of EXP's
operands. */
static rtx
expand_builtin_mathfn_3 (tree exp, rtx target, rtx subtarget)
{
optab builtin_optab;
rtx op0;
rtx_insn *insns;
tree fndecl = get_callee_fndecl (exp);
machine_mode mode;
tree arg;
if (!validate_arglist (exp, REAL_TYPE, VOID_TYPE))
return NULL_RTX;
arg = CALL_EXPR_ARG (exp, 0);
switch (DECL_FUNCTION_CODE (fndecl))
{
CASE_FLT_FN (BUILT_IN_SIN):
CASE_FLT_FN (BUILT_IN_COS):
builtin_optab = sincos_optab; break;
default:
gcc_unreachable ();
}
/* Make a suitable register to place result in. */
mode = TYPE_MODE (TREE_TYPE (exp));
/* Check if sincos insn is available, otherwise fallback
to sin or cos insn. */
if (optab_handler (builtin_optab, mode) == CODE_FOR_nothing)
switch (DECL_FUNCTION_CODE (fndecl))
{
CASE_FLT_FN (BUILT_IN_SIN):
builtin_optab = sin_optab; break;
CASE_FLT_FN (BUILT_IN_COS):
builtin_optab = cos_optab; break;
default:
gcc_unreachable ();
}
/* Before working hard, check whether the instruction is available. */
if (optab_handler (builtin_optab, mode) != CODE_FOR_nothing)
{
rtx result = gen_reg_rtx (mode);
/* Wrap the computation of the argument in a SAVE_EXPR, as we may
need to expand the argument again. This way, we will not perform
side-effects more the once. */
CALL_EXPR_ARG (exp, 0) = arg = builtin_save_expr (arg);
op0 = expand_expr (arg, subtarget, VOIDmode, EXPAND_NORMAL);
start_sequence ();
/* Compute into RESULT.
Set RESULT to wherever the result comes back. */
if (builtin_optab == sincos_optab)
{
int ok;
switch (DECL_FUNCTION_CODE (fndecl))
{
CASE_FLT_FN (BUILT_IN_SIN):
ok = expand_twoval_unop (builtin_optab, op0, 0, result, 0);
break;
CASE_FLT_FN (BUILT_IN_COS):
ok = expand_twoval_unop (builtin_optab, op0, result, 0, 0);
break;
default:
gcc_unreachable ();
}
gcc_assert (ok);
}
else
result = expand_unop (mode, builtin_optab, op0, result, 0);
if (result != 0)
{
/* Output the entire sequence. */
insns = get_insns ();
end_sequence ();
emit_insn (insns);
return result;
}
/* If we were unable to expand via the builtin, stop the sequence
(without outputting the insns) and call to the library function
with the stabilized argument list. */
end_sequence ();
}
return expand_call (exp, target, target == const0_rtx);
}
/* Given an interclass math builtin decl FNDECL and it's argument ARG
return an RTL instruction code that implements the functionality.
If that isn't possible or available return CODE_FOR_nothing. */
static enum insn_code
interclass_mathfn_icode (tree arg, tree fndecl)
{
bool errno_set = false;
optab builtin_optab = unknown_optab;
machine_mode mode;
switch (DECL_FUNCTION_CODE (fndecl))
{
CASE_FLT_FN (BUILT_IN_ILOGB):
errno_set = true; builtin_optab = ilogb_optab; break;
CASE_FLT_FN (BUILT_IN_ISINF):
builtin_optab = isinf_optab; break;
case BUILT_IN_ISNORMAL:
case BUILT_IN_ISFINITE:
CASE_FLT_FN (BUILT_IN_FINITE):
case BUILT_IN_FINITED32:
case BUILT_IN_FINITED64:
case BUILT_IN_FINITED128:
case BUILT_IN_ISINFD32:
case BUILT_IN_ISINFD64:
case BUILT_IN_ISINFD128:
/* These builtins have no optabs (yet). */
break;
default:
gcc_unreachable ();
}
/* There's no easy way to detect the case we need to set EDOM. */
if (flag_errno_math && errno_set)
return CODE_FOR_nothing;
/* Optab mode depends on the mode of the input argument. */
mode = TYPE_MODE (TREE_TYPE (arg));
if (builtin_optab)
return optab_handler (builtin_optab, mode);
return CODE_FOR_nothing;