From 2a837de28ee94b4ec201059a9a7aaa852e6808da Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Wed, 28 Jul 2021 15:28:10 -0600 Subject: [PATCH] Add new gimple-ssa-warn-access pass. gcc/ChangeLog: * Makefile.in (OBJS): Add gimple-ssa-warn-access.o and pointer-query.o. * attribs.h (fndecl_dealloc_argno): Move fndecl_dealloc_argno to tree.h. * builtins.c (compute_objsize_r): Move to pointer-query.cc. (access_ref::access_ref): Same. (access_ref::phi): Same. (access_ref::get_ref): Same. (access_ref::size_remaining): Same. (access_ref::offset_in_range): Same. (access_ref::add_offset): Same. (access_ref::inform_access): Same. (ssa_name_limit_t::visit_phi): Same. (ssa_name_limit_t::leave_phi): Same. (ssa_name_limit_t::next): Same. (ssa_name_limit_t::next_phi): Same. (ssa_name_limit_t::~ssa_name_limit_t): Same. (pointer_query::pointer_query): Same. (pointer_query::get_ref): Same. (pointer_query::put_ref): Same. (pointer_query::flush_cache): Same. (warn_string_no_nul): Move to gimple-ssa-warn-access.cc. (check_nul_terminated_array): Same. (unterminated_array): Same. (maybe_warn_for_bound): Same. (check_read_access): Same. (warn_for_access): Same. (get_size_range): Same. (check_access): Same. (gimple_call_alloc_size): Move to tree.c. (gimple_parm_array_size): Move to pointer-query.cc. (get_offset_range): Same. (gimple_call_return_array): Same. (handle_min_max_size): Same. (handle_array_ref): Same. (handle_mem_ref): Same. (compute_objsize): Same. (gimple_call_alloc_p): Move to gimple-ssa-warn-access.cc. (call_dealloc_argno): Same. (fndecl_dealloc_argno): Same. (new_delete_mismatch_p): Same. (matching_alloc_calls_p): Same. (warn_dealloc_offset): Same. (maybe_emit_free_warning): Same. * builtins.h (check_nul_terminated_array): Move to gimple-ssa-warn-access.h. (check_nul_terminated_array): Same. (warn_string_no_nul): Same. (unterminated_array): Same. (class ssa_name_limit_t): Same. (class pointer_query): Same. (struct access_ref): Same. (class range_query): Same. (struct access_data): Same. (gimple_call_alloc_size): Same. (gimple_parm_array_size): Same. (compute_objsize): Same. (class access_data): Same. (maybe_emit_free_warning): Same. * calls.c (initialize_argument_information): Remove call to maybe_emit_free_warning. * gimple-array-bounds.cc: Include new header.. * gimple-fold.c: Same. * gimple-ssa-sprintf.c: Same. * gimple-ssa-warn-restrict.c: Same. * passes.def: Add pass_warn_access. * tree-pass.h (make_pass_warn_access): Declare. * tree-ssa-strlen.c: Include new headers. * tree.c (fndecl_dealloc_argno): Move here from builtins.c. * tree.h (fndecl_dealloc_argno): Move here from attribs.h. * gimple-ssa-warn-access.cc: New file. * gimple-ssa-warn-access.h: New file. * pointer-query.cc: New file. * pointer-query.h: New file. gcc/cp/ChangeLog: * init.c: Include new header. --- gcc/Makefile.in | 2 + gcc/attribs.h | 2 - gcc/builtins.c | 15209 ++++++++++++------------------- gcc/builtins.h | 215 +- gcc/calls.c | 5 +- gcc/cp/init.c | 2 +- gcc/gimple-array-bounds.cc | 2 +- gcc/gimple-fold.c | 1 + gcc/gimple-ssa-sprintf.c | 1 + gcc/gimple-ssa-warn-access.cc | 1765 ++++ gcc/gimple-ssa-warn-access.h | 37 + gcc/gimple-ssa-warn-restrict.c | 2 +- gcc/passes.def | 1 + gcc/pointer-query.cc | 1895 ++++ gcc/pointer-query.h | 234 + gcc/tree-pass.h | 1 + gcc/tree-ssa-strlen.c | 2 + gcc/tree.c | 59 + gcc/tree.h | 5 + 19 files changed, 9855 insertions(+), 9585 deletions(-) create mode 100644 gcc/gimple-ssa-warn-access.cc create mode 100644 gcc/gimple-ssa-warn-access.h create mode 100644 gcc/pointer-query.cc create mode 100644 gcc/pointer-query.h diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 29bd4edb4b7e..98eb479a125e 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1414,6 +1414,7 @@ OBJS = \ gimple-ssa-store-merging.o \ gimple-ssa-strength-reduction.o \ gimple-ssa-sprintf.o \ + gimple-ssa-warn-access.o \ gimple-ssa-warn-alloca.o \ gimple-ssa-warn-restrict.o \ gimple-streamer-in.o \ @@ -1524,6 +1525,7 @@ OBJS = \ ordered-hash-map-tests.o \ passes.o \ plugin.o \ + pointer-query.o \ postreload-gcse.o \ postreload.o \ predict.o \ diff --git a/gcc/attribs.h b/gcc/attribs.h index df78eb152f9b..87231b954c6e 100644 --- a/gcc/attribs.h +++ b/gcc/attribs.h @@ -316,6 +316,4 @@ extern void init_attr_rdwr_indices (rdwr_map *, tree); extern attr_access *get_parm_access (rdwr_map &, tree, tree = current_function_decl); -extern unsigned fndecl_dealloc_argno (tree fndecl); - #endif // GCC_ATTRIBS_H diff --git a/gcc/builtins.c b/gcc/builtins.c index 170d776c4100..845a8bb12012 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -80,6 +80,8 @@ along with GCC; see the file COPYING3. If not see #include "attr-fnspec.h" #include "demangle.h" #include "gimple-range.h" +#include "pointer-query.h" +#include "gimple-ssa-warn-access.h" struct target_builtins default_target_builtins; #if SWITCHABLE_TARGET @@ -185,8 +187,6 @@ 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; @@ -199,565 +199,6 @@ 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; - offmax[0] = offmax[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 (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 *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; -} - -/* Return true if the offset and object size are in range for SIZE. */ - -bool -access_ref::offset_in_range (const offset_int &size) const -{ - if (size_remaining () < size) - return false; - - if (base0) - return offmax[0] >= 0 && offmax[1] <= sizrng[1]; - - offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - return offmax[0] > -maxoff && offmax[1] < maxoff; -} - -/* 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; - if (offmax[0] > 0) - offmax[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; - } - - /* Set the minimum and maximmum computed so far. */ - if (offrng[1] < 0 && offrng[1] < offmax[0]) - offmax[0] = offrng[1]; - if (offrng[0] > 0 && offrng[0] > offmax[1]) - offmax[1] = offrng[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 @@ -1106,218 +547,6 @@ string_length (const void *ptr, unsigned eltsize, unsigned maxelts) 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 */) -{ - const opt_code opt = OPT_Wstringop_overread; - if ((expr && warning_suppressed_p (expr, opt)) - || warning_suppressed_p (arg, opt)) - 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, - "%qD specified bound %s exceeds " - "maximum object size %E", - func, bndstr, maxobjsize); - else - { - bool maybe = wi::to_wide (size) == bndrng[0]; - warned = warning_at (loc, opt, - exact - ? G_("%qD specified bound %s exceeds " - "the size %E of unterminated array") - : (maybe - ? G_("%qD specified bound %s may " - "exceed the size of at most %E " - "of unterminated array") - : G_("%qD specified bound %s exceeds " - "the size of at most %E " - "of unterminated array")), - func, bndstr, size); - } - } - else - warned = warning_at (loc, opt, - "%qD argument missing terminating nul", - func); - } - else - { - if (bndrng) - { - if (wi::ltu_p (maxsiz, bndrng[0])) - warned = warning_at (loc, opt, - "%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, - 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, - "%qs argument missing terminating nul", - fname); - } - - if (warned) - { - inform (DECL_SOURCE_LOCATION (decl), - "referenced argument declared here"); - suppress_warning (arg, opt); - if (expr) - suppress_warning (expr, opt); - } -} - -/* 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) - { - value_range r; - - get_global_range_query ()->range_of_expr (r, bound); - - if (r.kind () != VR_RANGE) - return true; - - bndrng[0] = r.lower_bound (); - bndrng[1] = r.upper_bound (); - - 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 @@ -3969,10097 +3198,7353 @@ determine_block_size (tree len, rtx len_rtx, GET_MODE_MASK (GET_MODE (len_rtx))); } -/* Issue a warning OPT for a bounded call EXP with a bound in RANGE - accessing an object with SIZE. */ - +/* A convenience wrapper for check_access above to check access + by a read-only function like puts. */ + static bool -maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func, - tree bndrng[2], tree size, const access_data *pad = NULL) +check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */, + int ost /* = 1 */) { - if (!bndrng[0] || warning_suppressed_p (exp, opt)) - return false; - - tree maxobjsize = max_object_size (); - - bool warned = false; - - if (opt == OPT_Wstringop_overread) - { - bool maybe = pad && pad->src.phi (); - - if (tree_int_cst_lt (maxobjsize, bndrng[0])) - { - if (bndrng[0] == bndrng[1]) - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound %E may " - "exceed maximum object size %E") - : G_("%qD specified bound %E " - "exceeds maximum object size %E")), - func, bndrng[0], maxobjsize) - : warning_at (loc, opt, - (maybe - ? G_("specified bound %E may " - "exceed maximum object size %E") - : G_("specified bound %E " - "exceeds maximum object size %E")), - bndrng[0], maxobjsize)); - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound [%E, %E] may " - "exceed maximum object size %E") - : G_("%qD specified bound [%E, %E] " - "exceeds maximum object size %E")), - func, bndrng[0], bndrng[1], maxobjsize) - : warning_at (loc, opt, - (maybe - ? G_("specified bound [%E, %E] may " - "exceed maximum object size %E") - : G_("specified bound [%E, %E] " - "exceeds maximum object size %E")), - bndrng[0], bndrng[1], maxobjsize)); - } - else if (!size || tree_int_cst_le (bndrng[0], size)) - return false; - else if (tree_int_cst_equal (bndrng[0], bndrng[1])) - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound %E may exceed " - "source size %E") - : G_("%qD specified bound %E exceeds " - "source size %E")), - func, bndrng[0], size) - : warning_at (loc, opt, - (maybe - ? G_("specified bound %E may exceed " - "source size %E") - : G_("specified bound %E exceeds " - "source size %E")), - bndrng[0], size)); - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound [%E, %E] may " - "exceed source size %E") - : G_("%qD specified bound [%E, %E] exceeds " - "source size %E")), - func, bndrng[0], bndrng[1], size) - : warning_at (loc, opt, - (maybe - ? G_("specified bound [%E, %E] may exceed " - "source size %E") - : G_("specified bound [%E, %E] exceeds " - "source size %E")), - bndrng[0], bndrng[1], size)); - if (warned) - { - if (pad && pad->src.ref) - { - if (DECL_P (pad->src.ref)) - inform (DECL_SOURCE_LOCATION (pad->src.ref), - "source object declared here"); - else if (EXPR_HAS_LOCATION (pad->src.ref)) - inform (EXPR_LOCATION (pad->src.ref), - "source object allocated here"); - } - suppress_warning (exp, opt); - } - - return warned; - } - - bool maybe = pad && pad->dst.phi (); - if (tree_int_cst_lt (maxobjsize, bndrng[0])) - { - if (bndrng[0] == bndrng[1]) - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified size %E may " - "exceed maximum object size %E") - : G_("%qD specified size %E " - "exceeds maximum object size %E")), - func, bndrng[0], maxobjsize) - : warning_at (loc, opt, - (maybe - ? G_("specified size %E may exceed " - "maximum object size %E") - : G_("specified size %E exceeds " - "maximum object size %E")), - bndrng[0], maxobjsize)); - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified size between %E and %E " - "may exceed maximum object size %E") - : G_("%qD specified size between %E and %E " - "exceeds maximum object size %E")), - func, bndrng[0], bndrng[1], maxobjsize) - : warning_at (loc, opt, - (maybe - ? G_("specified size between %E and %E " - "may exceed maximum object size %E") - : G_("specified size between %E and %E " - "exceeds maximum object size %E")), - bndrng[0], bndrng[1], maxobjsize)); - } - else if (!size || tree_int_cst_le (bndrng[0], size)) - return false; - else if (tree_int_cst_equal (bndrng[0], bndrng[1])) - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound %E may exceed " - "destination size %E") - : G_("%qD specified bound %E exceeds " - "destination size %E")), - func, bndrng[0], size) - : warning_at (loc, opt, - (maybe - ? G_("specified bound %E may exceed " - "destination size %E") - : G_("specified bound %E exceeds " - "destination size %E")), - bndrng[0], size)); - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD specified bound [%E, %E] may exceed " - "destination size %E") - : G_("%qD specified bound [%E, %E] exceeds " - "destination size %E")), - func, bndrng[0], bndrng[1], size) - : warning_at (loc, opt, - (maybe - ? G_("specified bound [%E, %E] exceeds " - "destination size %E") - : G_("specified bound [%E, %E] exceeds " - "destination size %E")), - bndrng[0], bndrng[1], size)); - - if (warned) - { - if (pad && pad->dst.ref) - { - if (DECL_P (pad->dst.ref)) - inform (DECL_SOURCE_LOCATION (pad->dst.ref), - "destination object declared here"); - else if (EXPR_HAS_LOCATION (pad->dst.ref)) - inform (EXPR_LOCATION (pad->dst.ref), - "destination object allocated here"); - } - suppress_warning (exp, opt); - } + if (!warn_stringop_overread) + return true; - return warned; + if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound))) + bound = fold_convert (size_type_node, bound); + access_data data (exp, access_read_only, NULL_TREE, false, bound, true); + compute_objsize (src, ost, &data.src); + return check_access (exp, /*dstwrite=*/ NULL_TREE, /*maxread=*/ bound, + /*srcstr=*/ src, /*dstsize=*/ NULL_TREE, data.mode, + &data); } -/* For an expression EXP issue an access warning controlled by option OPT - with access to a region SIZE bytes in size in the RANGE of sizes. - WRITE is true for a write access, READ for a read access, neither for - call that may or may not perform an access but for which the range - is expected to valid. - Returns true when a warning has been issued. */ +/* Helper to determine and check the sizes of the source and the destination + of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls. EXP is the + call expression, DEST is the destination argument, SRC is the source + argument or null, and LEN is the number of bytes. Use Object Size type-0 + regardless of the OPT_Wstringop_overflow_ setting. Return true on success + (no overflow or invalid sizes), false otherwise. */ static bool -warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], - tree size, bool write, bool read, bool maybe) -{ - bool warned = false; - - if (write && read) - { - if (tree_int_cst_equal (range[0], range[1])) - warned = (func - ? warning_n (loc, opt, tree_to_uhwi (range[0]), - (maybe - ? G_("%qD may access %E byte in a region " - "of size %E") - : G_("%qD accessing %E byte in a region " - "of size %E")), - (maybe - ? G_ ("%qD may access %E bytes in a region " - "of size %E") - : G_ ("%qD accessing %E bytes in a region " - "of size %E")), - func, range[0], size) - : warning_n (loc, opt, tree_to_uhwi (range[0]), - (maybe - ? G_("may access %E byte in a region " - "of size %E") - : G_("accessing %E byte in a region " - "of size %E")), - (maybe - ? G_("may access %E bytes in a region " - "of size %E") - : G_("accessing %E bytes in a region " - "of size %E")), - range[0], size)); - else if (tree_int_cst_sign_bit (range[1])) - { - /* Avoid printing the upper bound if it's invalid. */ - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD may access %E or more bytes " - "in a region of size %E") - : G_("%qD accessing %E or more bytes " - "in a region of size %E")), - func, range[0], size) - : warning_at (loc, opt, - (maybe - ? G_("may access %E or more bytes " - "in a region of size %E") - : G_("accessing %E or more bytes " - "in a region of size %E")), - range[0], size)); - } - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD may access between %E and %E " - "bytes in a region of size %E") - : G_("%qD accessing between %E and %E " - "bytes in a region of size %E")), - func, range[0], range[1], size) - : warning_at (loc, opt, - (maybe - ? G_("may access between %E and %E bytes " - "in a region of size %E") - : G_("accessing between %E and %E bytes " - "in a region of size %E")), - range[0], range[1], size)); - return warned; - } - - if (write) - { - if (tree_int_cst_equal (range[0], range[1])) - warned = (func - ? warning_n (loc, opt, tree_to_uhwi (range[0]), - (maybe - ? G_("%qD may write %E byte into a region " - "of size %E") - : G_("%qD writing %E byte into a region " - "of size %E overflows the destination")), - (maybe - ? G_("%qD may write %E bytes into a region " - "of size %E") - : G_("%qD writing %E bytes into a region " - "of size %E overflows the destination")), - func, range[0], size) - : warning_n (loc, opt, tree_to_uhwi (range[0]), - (maybe - ? G_("may write %E byte into a region " - "of size %E") - : G_("writing %E byte into a region " - "of size %E overflows the destination")), - (maybe - ? G_("may write %E bytes into a region " - "of size %E") - : G_("writing %E bytes into a region " - "of size %E overflows the destination")), - range[0], size)); - else if (tree_int_cst_sign_bit (range[1])) - { - /* Avoid printing the upper bound if it's invalid. */ - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD may write %E or more bytes " - "into a region of size %E") - : G_("%qD writing %E or more bytes " - "into a region of size %E overflows " - "the destination")), - func, range[0], size) - : warning_at (loc, opt, - (maybe - ? G_("may write %E or more bytes into " - "a region of size %E") - : G_("writing %E or more bytes into " - "a region of size %E overflows " - "the destination")), - range[0], size)); - } - else - warned = (func - ? warning_at (loc, opt, - (maybe - ? G_("%qD may write between %E and %E bytes " - "into a region of size %E") - : G_("%qD writing between %E and %E bytes " - "into a region of size %E overflows " - "the destination")), - func, range[0], range[1], size) - : warning_at (loc, opt, - (maybe - ? G_("may write between %E and %E bytes " - "into a region of size %E") - : G_("writing between %E and %E bytes " - "into a region of size %E overflows " - "the destination")), - range[0], range[1], size)); - return warned; - } - - if (read) - { - if (tree_int_cst_equal (range[0], range[1])) - warned = (func - ? warning_n (loc, OPT_Wstringop_overread, - tree_to_uhwi (range[0]), - (maybe - ? G_("%qD may read %E byte from a region " - "of size %E") - : G_("%qD reading %E byte from a region " - "of size %E")), - (maybe - ? G_("%qD may read %E bytes from a region " - "of size %E") - : G_("%qD reading %E bytes from a region " - "of size %E")), - func, range[0], size) - : warning_n (loc, OPT_Wstringop_overread, - tree_to_uhwi (range[0]), - (maybe - ? G_("may read %E byte from a region " - "of size %E") - : G_("reading %E byte from a region " - "of size %E")), - (maybe - ? G_("may read %E bytes from a region " - "of size %E") - : G_("reading %E bytes from a region " - "of size %E")), - range[0], size)); - else if (tree_int_cst_sign_bit (range[1])) - { - /* Avoid printing the upper bound if it's invalid. */ - warned = (func - ? warning_at (loc, OPT_Wstringop_overread, - (maybe - ? G_("%qD may read %E or more bytes " - "from a region of size %E") - : G_("%qD reading %E or more bytes " - "from a region of size %E")), - func, range[0], size) - : warning_at (loc, OPT_Wstringop_overread, - (maybe - ? G_("may read %E or more bytes " - "from a region of size %E") - : G_("reading %E or more bytes " - "from a region of size %E")), - range[0], size)); - } - else - warned = (func - ? warning_at (loc, OPT_Wstringop_overread, - (maybe - ? G_("%qD may read between %E and %E bytes " - "from a region of size %E") - : G_("%qD reading between %E and %E bytes " - "from a region of size %E")), - func, range[0], range[1], size) - : warning_at (loc, opt, - (maybe - ? G_("may read between %E and %E bytes " - "from a region of size %E") - : G_("reading between %E and %E bytes " - "from a region of size %E")), - range[0], range[1], size)); - - if (warned) - suppress_warning (exp, OPT_Wstringop_overread); - - return warned; - } - - if (tree_int_cst_equal (range[0], range[1]) - || tree_int_cst_sign_bit (range[1])) - warned = (func - ? warning_n (loc, OPT_Wstringop_overread, - tree_to_uhwi (range[0]), - "%qD expecting %E byte in a region of size %E", - "%qD expecting %E bytes in a region of size %E", - func, range[0], size) - : warning_n (loc, OPT_Wstringop_overread, - tree_to_uhwi (range[0]), - "expecting %E byte in a region of size %E", - "expecting %E bytes in a region of size %E", - range[0], size)); - else if (tree_int_cst_sign_bit (range[1])) - { - /* Avoid printing the upper bound if it's invalid. */ - warned = (func - ? warning_at (loc, OPT_Wstringop_overread, - "%qD expecting %E or more bytes in a region " - "of size %E", - func, range[0], size) - : warning_at (loc, OPT_Wstringop_overread, - "expecting %E or more bytes in a region " - "of size %E", - range[0], size)); - } - else - warned = (func - ? warning_at (loc, OPT_Wstringop_overread, - "%qD expecting between %E and %E bytes in " - "a region of size %E", - func, range[0], range[1], size) - : warning_at (loc, OPT_Wstringop_overread, - "expecting between %E and %E bytes in " - "a region of size %E", - range[0], range[1], size)); - - if (warned) - suppress_warning (exp, OPT_Wstringop_overread); +check_memop_access (tree exp, tree dest, tree src, tree size) +{ + /* For functions like memset and memcpy that operate on raw memory + try to determine the size of the largest source and destination + object using type-0 Object Size regardless of the object size + type specified by the option. */ + access_data data (exp, access_read_write); + tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE; + tree dstsize = compute_objsize (dest, 0, &data.dst); - return warned; + return check_access (exp, size, /*maxread=*/NULL_TREE, + srcsize, dstsize, data.mode, &data); } -/* Issue one inform message describing each target of an access REF. - WRITE is set for a write access and clear for a read access. */ +/* Validate memchr arguments without performing any expansion. + Return NULL_RTX. */ -void -access_ref::inform_access (access_mode mode) const +static rtx +expand_builtin_memchr (tree exp, rtx) { - const access_ref &aref = *this; - if (!aref.ref) - return; - - if (aref.phi ()) - { - /* Set MAXREF to refer to the largest object and fill ALL_REFS - with data for all objects referenced by the PHI arguments. */ - access_ref maxref; - auto_vec all_refs; - if (!get_ref (&all_refs, &maxref)) - return; - - /* Except for MAXREF, the rest of the arguments' offsets need not - reflect one added to the PHI itself. Determine the latter from - MAXREF on which the result is based. */ - const offset_int orng[] = - { - offrng[0] - maxref.offrng[0], - wi::smax (offrng[1] - maxref.offrng[1], offrng[0]), - }; - - /* Add the final PHI's offset to that of each of the arguments - and recurse to issue an inform message for it. */ - for (unsigned i = 0; i != all_refs.length (); ++i) - { - /* Skip any PHIs; those could lead to infinite recursion. */ - if (all_refs[i].phi ()) - continue; - - all_refs[i].add_offset (orng[0], orng[1]); - all_refs[i].inform_access (mode); - } - return; - } - - /* Convert offset range and avoid including a zero range since it - isn't necessarily meaningful. */ - HOST_WIDE_INT diff_min = tree_to_shwi (TYPE_MIN_VALUE (ptrdiff_type_node)); - HOST_WIDE_INT diff_max = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); - HOST_WIDE_INT minoff; - HOST_WIDE_INT maxoff = diff_max; - if (wi::fits_shwi_p (aref.offrng[0])) - minoff = aref.offrng[0].to_shwi (); - else - minoff = aref.offrng[0] < 0 ? diff_min : diff_max; - - if (wi::fits_shwi_p (aref.offrng[1])) - maxoff = aref.offrng[1].to_shwi (); - - if (maxoff <= diff_min || maxoff >= diff_max) - /* Avoid mentioning an upper bound that's equal to or in excess - of the maximum of ptrdiff_t. */ - maxoff = minoff; + if (!validate_arglist (exp, + POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - /* Convert size range and always include it since all sizes are - meaningful. */ - unsigned long long minsize = 0, maxsize = 0; - if (wi::fits_shwi_p (aref.sizrng[0]) - && wi::fits_shwi_p (aref.sizrng[1])) - { - minsize = aref.sizrng[0].to_shwi (); - maxsize = aref.sizrng[1].to_shwi (); - } + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree len = CALL_EXPR_ARG (exp, 2); - /* SIZRNG doesn't necessarily have the same range as the allocation - size determined by gimple_call_alloc_size (). */ - char sizestr[80]; - if (minsize == maxsize) - sprintf (sizestr, "%llu", minsize); - else - sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); - - char offstr[80]; - if (minoff == 0 - && (maxoff == 0 || aref.sizrng[1] <= maxoff)) - offstr[0] = '\0'; - else if (minoff == maxoff) - sprintf (offstr, "%lli", (long long) minoff); - else - sprintf (offstr, "[%lli, %lli]", (long long) minoff, (long long) maxoff); + check_read_access (exp, arg1, len, 0); - location_t loc = UNKNOWN_LOCATION; + return NULL_RTX; +} - tree ref = this->ref; - tree allocfn = NULL_TREE; - if (TREE_CODE (ref) == SSA_NAME) - { - gimple *stmt = SSA_NAME_DEF_STMT (ref); - if (is_gimple_call (stmt)) - { - loc = gimple_location (stmt); - if (gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) - { - /* Strip the SSA_NAME suffix from the variable name and - recreate an identifier with the VLA's original name. */ - ref = gimple_call_lhs (stmt); - if (SSA_NAME_IDENTIFIER (ref)) - { - ref = SSA_NAME_IDENTIFIER (ref); - const char *id = IDENTIFIER_POINTER (ref); - size_t len = strcspn (id, ".$"); - if (!len) - len = strlen (id); - ref = get_identifier_with_length (id, len); - } - } - else - { - /* Except for VLAs, retrieve the allocation function. */ - allocfn = gimple_call_fndecl (stmt); - if (!allocfn) - allocfn = gimple_call_fn (stmt); - if (TREE_CODE (allocfn) == SSA_NAME) - { - /* For an ALLOC_CALL via a function pointer make a small - effort to determine the destination of the pointer. */ - gimple *def = SSA_NAME_DEF_STMT (allocfn); - if (gimple_assign_single_p (def)) - { - tree rhs = gimple_assign_rhs1 (def); - if (DECL_P (rhs)) - allocfn = rhs; - else if (TREE_CODE (rhs) == COMPONENT_REF) - allocfn = TREE_OPERAND (rhs, 1); - } - } - } - } - else if (gimple_nop_p (stmt)) - /* Handle DECL_PARM below. */ - ref = SSA_NAME_VAR (ref); - } +/* Expand a call EXP to the memcpy builtin. + Return NULL_RTX if we failed, the caller should emit a normal call, + otherwise try to get the result in TARGET, if convenient (and in + mode MODE if that's convenient). */ - if (DECL_P (ref)) - loc = DECL_SOURCE_LOCATION (ref); - else if (EXPR_P (ref) && EXPR_HAS_LOCATION (ref)) - loc = EXPR_LOCATION (ref); - else if (TREE_CODE (ref) != IDENTIFIER_NODE - && TREE_CODE (ref) != SSA_NAME) - return; +static rtx +expand_builtin_memcpy (tree exp, rtx target) +{ + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - if (mode == access_read_write || mode == access_write_only) - { - if (allocfn == NULL_TREE) - { - if (*offstr) - inform (loc, "at offset %s into destination object %qE of size %s", - offstr, ref, sizestr); - else - inform (loc, "destination object %qE of size %s", ref, sizestr); - return; - } + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); - if (*offstr) - inform (loc, - "at offset %s into destination object of size %s " - "allocated by %qE", offstr, sizestr, allocfn); - else - inform (loc, "destination object of size %s allocated by %qE", - sizestr, allocfn); - return; - } + check_memop_access (exp, dest, src, len); - if (mode == access_read_only) - { - if (allocfn == NULL_TREE) - { - if (*offstr) - inform (loc, "at offset %s into source object %qE of size %s", - offstr, ref, sizestr); - else - inform (loc, "source object %qE of size %s", ref, sizestr); + return expand_builtin_memory_copy_args (dest, src, len, target, exp, + /*retmode=*/ RETURN_BEGIN, false); +} - return; - } +/* Check a call EXP to the memmove built-in for validity. + Return NULL_RTX on both success and failure. */ - if (*offstr) - inform (loc, - "at offset %s into source object of size %s allocated by %qE", - offstr, sizestr, allocfn); - else - inform (loc, "source object of size %s allocated by %qE", - sizestr, allocfn); - return; - } +static rtx +expand_builtin_memmove (tree exp, rtx target) +{ + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - if (allocfn == NULL_TREE) - { - if (*offstr) - inform (loc, "at offset %s into object %qE of size %s", - offstr, ref, sizestr); - else - inform (loc, "object %qE of size %s", ref, sizestr); + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); - return; - } + check_memop_access (exp, dest, src, len); - if (*offstr) - inform (loc, - "at offset %s into object of size %s allocated by %qE", - offstr, sizestr, allocfn); - else - inform (loc, "object of size %s allocated by %qE", - sizestr, allocfn); + return expand_builtin_memory_copy_args (dest, src, len, target, exp, + /*retmode=*/ RETURN_BEGIN, true); } -/* Helper to set RANGE to the range of BOUND if it's nonnull, bounded - by BNDRNG if nonnull and valid. */ +/* Expand a call EXP to the mempcpy builtin. + Return NULL_RTX if we failed; the caller should emit a normal call, + otherwise try to get the result in TARGET, if convenient (and in + mode MODE if that's convenient). */ -static void -get_size_range (tree bound, tree range[2], const offset_int bndrng[2]) +static rtx +expand_builtin_mempcpy (tree exp, rtx target) { - if (bound) - get_size_range (bound, range); - - if (!bndrng || (bndrng[0] == 0 && bndrng[1] == HOST_WIDE_INT_M1U)) - return; + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - if (range[0] && TREE_CODE (range[0]) == INTEGER_CST) - { - offset_int r[] = - { wi::to_offset (range[0]), wi::to_offset (range[1]) }; - if (r[0] < bndrng[0]) - range[0] = wide_int_to_tree (sizetype, bndrng[0]); - if (bndrng[1] < r[1]) - range[1] = wide_int_to_tree (sizetype, bndrng[1]); - } - else - { - range[0] = wide_int_to_tree (sizetype, bndrng[0]); - range[1] = wide_int_to_tree (sizetype, bndrng[1]); - } -} + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); -/* Try to verify that the sizes and lengths of the arguments to a string - manipulation function given by EXP are within valid bounds and that - the operation does not lead to buffer overflow or read past the end. - Arguments other than EXP may be null. When non-null, the arguments - have the following meaning: - DST is the destination of a copy call or NULL otherwise. - SRC is the source of a copy call or NULL otherwise. - DSTWRITE is the number of bytes written into the destination obtained - from the user-supplied size argument to the function (such as in - memcpy(DST, SRCs, DSTWRITE) or strncpy(DST, DRC, DSTWRITE). - MAXREAD is the user-supplied bound on the length of the source sequence - (such as in strncat(d, s, N). It specifies the upper limit on the number - of bytes to write. If NULL, it's taken to be the same as DSTWRITE. - SRCSTR is the source string (such as in strcpy(DST, SRC)) when the - expression EXP is a string function call (as opposed to a memory call - like memcpy). As an exception, SRCSTR can also be an integer denoting - the precomputed size of the source string or object (for functions like - memcpy). - DSTSIZE is the size of the destination object. + /* Policy does not generally allow using compute_objsize (which + is used internally by check_memop_size) to change code generation + or drive optimization decisions. - When DSTWRITE is null LEN is checked to verify that it doesn't exceed - SIZE_MAX. + In this instance it is safe because the code we generate has + the same semantics regardless of the return value of + check_memop_sizes. Exactly the same amount of data is copied + and the return value is exactly the same in both cases. - WRITE is true for write accesses, READ is true for reads. Both are - false for simple size checks in calls to functions that neither read - from nor write to the region. + Furthermore, check_memop_size always uses mode 0 for the call to + compute_objsize, so the imprecise nature of compute_objsize is + avoided. */ - When nonnull, PAD points to a more detailed description of the access. + /* Avoid expanding mempcpy into memcpy when the call is determined + to overflow the buffer. This also prevents the same overflow + from being diagnosed again when expanding memcpy. */ + if (!check_memop_access (exp, dest, src, len)) + return NULL_RTX; - If the call is successfully verified as safe return true, otherwise - return false. */ + return expand_builtin_mempcpy_args (dest, src, len, + target, exp, /*retmode=*/ RETURN_END); +} -bool -check_access (tree exp, tree dstwrite, - tree maxread, tree srcstr, tree dstsize, - access_mode mode, const access_data *pad /* = NULL */) -{ - /* The size of the largest object is half the address space, or - PTRDIFF_MAX. (This is way too permissive.) */ - tree maxobjsize = max_object_size (); - - /* Either an approximate/minimum the length of the source string for - string functions or the size of the source object for raw memory - functions. */ - tree slen = NULL_TREE; - - /* The range of the access in bytes; first set to the write access - for functions that write and then read for those that also (or - just) read. */ - tree range[2] = { NULL_TREE, NULL_TREE }; - - /* Set to true when the exact number of bytes written by a string - function like strcpy is not known and the only thing that is - known is that it must be at least one (for the terminating nul). */ - bool at_least_one = false; - if (srcstr) - { - /* SRCSTR is normally a pointer to string but as a special case - it can be an integer denoting the length of a string. */ - if (POINTER_TYPE_P (TREE_TYPE (srcstr))) - { - if (!check_nul_terminated_array (exp, srcstr, maxread)) - return false; - /* Try to determine the range of lengths the source string - refers to. If it can be determined and is less than - the upper bound given by MAXREAD add one to it for - the terminating nul. Otherwise, set it to one for - the same reason, or to MAXREAD as appropriate. */ - c_strlen_data lendata = { }; - get_range_strlen (srcstr, &lendata, /* eltsize = */ 1); - range[0] = lendata.minlen; - range[1] = lendata.maxbound ? lendata.maxbound : lendata.maxlen; - if (range[0] - && TREE_CODE (range[0]) == INTEGER_CST - && TREE_CODE (range[1]) == INTEGER_CST - && (!maxread || TREE_CODE (maxread) == INTEGER_CST)) - { - if (maxread && tree_int_cst_le (maxread, range[0])) - range[0] = range[1] = maxread; - else - range[0] = fold_build2 (PLUS_EXPR, size_type_node, - range[0], size_one_node); +/* Helper function to do the actual work for expand of memory copy family + functions (memcpy, mempcpy, stpcpy). Expansing should assign LEN bytes + of memory from SRC to DEST and assign to TARGET if convenient. Return + value is based on RETMODE argument. */ - if (maxread && tree_int_cst_le (maxread, range[1])) - range[1] = maxread; - else if (!integer_all_onesp (range[1])) - range[1] = fold_build2 (PLUS_EXPR, size_type_node, - range[1], size_one_node); +static rtx +expand_builtin_memory_copy_args (tree dest, tree src, tree len, + rtx target, tree exp, memop_ret retmode, + bool might_overlap) +{ + unsigned int src_align = get_pointer_alignment (src); + unsigned int dest_align = get_pointer_alignment (dest); + rtx dest_mem, src_mem, dest_addr, len_rtx; + HOST_WIDE_INT expected_size = -1; + unsigned int expected_align = 0; + unsigned HOST_WIDE_INT min_size; + unsigned HOST_WIDE_INT max_size; + unsigned HOST_WIDE_INT probable_max_size; - slen = range[0]; - } - else - { - at_least_one = true; - slen = size_one_node; - } - } - else - slen = srcstr; - } + bool is_move_done; - if (!dstwrite && !maxread) - { - /* When the only available piece of data is the object size - there is nothing to do. */ - if (!slen) - return true; + /* If DEST is not a pointer type, call the normal function. */ + if (dest_align == 0) + return NULL_RTX; - /* Otherwise, when the length of the source sequence is known - (as with strlen), set DSTWRITE to it. */ - if (!range[0]) - dstwrite = slen; - } + /* If either SRC is not a pointer type, don't do this + operation in-line. */ + if (src_align == 0) + return NULL_RTX; - if (!dstsize) - dstsize = maxobjsize; + if (currently_expanding_gimple_stmt) + stringop_block_profile (currently_expanding_gimple_stmt, + &expected_align, &expected_size); - /* Set RANGE to that of DSTWRITE if non-null, bounded by PAD->DST.BNDRNG - if valid. */ - get_size_range (dstwrite, range, pad ? pad->dst.bndrng : NULL); + if (expected_align < dest_align) + expected_align = dest_align; + dest_mem = get_memory_rtx (dest, len); + set_mem_align (dest_mem, dest_align); + len_rtx = expand_normal (len); + determine_block_size (len, len_rtx, &min_size, &max_size, + &probable_max_size); - tree func = get_callee_fndecl (exp); - /* Read vs write access by built-ins can be determined from the const - qualifiers on the pointer argument. In the absence of attribute - access, non-const qualified pointer arguments to user-defined - functions are assumed to both read and write the objects. */ - const bool builtin = func ? fndecl_built_in_p (func) : false; + /* Try to get the byte representation of the constant SRC points to, + with its byte size in NBYTES. */ + unsigned HOST_WIDE_INT nbytes; + const char *rep = getbyterep (src, &nbytes); - /* First check the number of bytes to be written against the maximum - object size. */ - if (range[0] - && TREE_CODE (range[0]) == INTEGER_CST - && tree_int_cst_lt (maxobjsize, range[0])) - { - location_t loc = EXPR_LOCATION (exp); - maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range, - NULL_TREE, pad); - return false; - } - - /* The number of bytes to write is "exact" if DSTWRITE is non-null, - constant, and in range of unsigned HOST_WIDE_INT. */ - bool exactwrite = dstwrite && tree_fits_uhwi_p (dstwrite); - - /* Next check the number of bytes to be written against the destination - object size. */ - if (range[0] || !exactwrite || integer_all_onesp (dstwrite)) + /* If the function's constant bound LEN_RTX is less than or equal + to the byte size of the representation of the constant argument, + and if block move would be done by pieces, we can avoid loading + the bytes from memory and only store the computed constant. + This works in the overlap (memmove) case as well because + store_by_pieces just generates a series of stores of constants + from the representation returned by getbyterep(). */ + if (rep + && CONST_INT_P (len_rtx) + && (unsigned HOST_WIDE_INT) INTVAL (len_rtx) <= nbytes + && can_store_by_pieces (INTVAL (len_rtx), builtin_memcpy_read_str, + CONST_CAST (char *, rep), + dest_align, false)) { - if (range[0] - && TREE_CODE (range[0]) == INTEGER_CST - && ((tree_fits_uhwi_p (dstsize) - && tree_int_cst_lt (dstsize, range[0])) - || (dstwrite - && tree_fits_uhwi_p (dstwrite) - && tree_int_cst_lt (dstwrite, range[0])))) - { - const opt_code opt = OPT_Wstringop_overflow_; - if (warning_suppressed_p (exp, opt) - || (pad && pad->dst.ref - && warning_suppressed_p (pad->dst.ref, opt))) - return false; - - location_t loc = EXPR_LOCATION (exp); - bool warned = false; - if (dstwrite == slen && at_least_one) - { - /* This is a call to strcpy with a destination of 0 size - and a source of unknown length. The call will write - at least one byte past the end of the destination. */ - warned = (func - ? warning_at (loc, opt, - "%qD writing %E or more bytes into " - "a region of size %E overflows " - "the destination", - func, range[0], dstsize) - : warning_at (loc, opt, - "writing %E or more bytes into " - "a region of size %E overflows " - "the destination", - range[0], dstsize)); - } - else - { - const bool read - = mode == access_read_only || mode == access_read_write; - const bool write - = mode == access_write_only || mode == access_read_write; - const bool maybe = pad && pad->dst.parmarray; - warned = warn_for_access (loc, func, exp, - OPT_Wstringop_overflow_, - range, dstsize, - write, read && !builtin, maybe); - } - - if (warned) - { - suppress_warning (exp, OPT_Wstringop_overflow_); - if (pad) - pad->dst.inform_access (pad->mode); - } - - /* Return error when an overflow has been detected. */ - return false; - } + dest_mem = store_by_pieces (dest_mem, INTVAL (len_rtx), + builtin_memcpy_read_str, + CONST_CAST (char *, rep), + dest_align, false, retmode); + dest_mem = force_operand (XEXP (dest_mem, 0), target); + dest_mem = convert_memory_address (ptr_mode, dest_mem); + return dest_mem; } - /* Check the maximum length of the source sequence against the size - of the destination object if known, or against the maximum size - of an object. */ - if (maxread) - { - /* Set RANGE to that of MAXREAD, bounded by PAD->SRC.BNDRNG if - PAD is nonnull and BNDRNG is valid. */ - get_size_range (maxread, range, pad ? pad->src.bndrng : NULL); + src_mem = get_memory_rtx (src, len); + set_mem_align (src_mem, src_align); - location_t loc = EXPR_LOCATION (exp); - tree size = dstsize; - if (pad && pad->mode == access_read_only) - size = wide_int_to_tree (sizetype, pad->src.sizrng[1]); + /* Copy word part most expediently. */ + enum block_op_methods method = BLOCK_OP_NORMAL; + if (CALL_EXPR_TAILCALL (exp) + && (retmode == RETURN_BEGIN || target == const0_rtx)) + method = BLOCK_OP_TAILCALL; + bool use_mempcpy_call = (targetm.libc_has_fast_function (BUILT_IN_MEMPCPY) + && retmode == RETURN_END + && !might_overlap + && target != const0_rtx); + if (use_mempcpy_call) + method = BLOCK_OP_NO_LIBCALL_RET; + dest_addr = emit_block_move_hints (dest_mem, src_mem, len_rtx, method, + expected_align, expected_size, + min_size, max_size, probable_max_size, + use_mempcpy_call, &is_move_done, + might_overlap); - if (range[0] && maxread && tree_fits_uhwi_p (size)) - { - if (tree_int_cst_lt (maxobjsize, range[0])) - { - maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, - range, size, pad); - return false; - } + /* Bail out when a mempcpy call would be expanded as libcall and when + we have a target that provides a fast implementation + of mempcpy routine. */ + if (!is_move_done) + return NULL_RTX; - if (size != maxobjsize && tree_int_cst_lt (size, range[0])) - { - opt_code opt = (dstwrite || mode != access_read_only - ? OPT_Wstringop_overflow_ - : OPT_Wstringop_overread); - maybe_warn_for_bound (opt, loc, exp, func, range, size, pad); - return false; - } - } + if (dest_addr == pc_rtx) + return NULL_RTX; - maybe_warn_nonstring_arg (func, exp); - } - - /* Check for reading past the end of SRC. */ - bool overread = (slen - && slen == srcstr - && dstwrite - && range[0] - && TREE_CODE (slen) == INTEGER_CST - && tree_int_cst_lt (slen, range[0])); - /* If none is determined try to get a better answer based on the details - in PAD. */ - if (!overread - && pad - && pad->src.sizrng[1] >= 0 - && pad->src.offrng[0] >= 0 - && (pad->src.offrng[1] < 0 - || pad->src.offrng[0] <= pad->src.offrng[1])) - { - /* Set RANGE to that of MAXREAD, bounded by PAD->SRC.BNDRNG if - PAD is nonnull and BNDRNG is valid. */ - get_size_range (maxread, range, pad ? pad->src.bndrng : NULL); - /* Set OVERREAD for reads starting just past the end of an object. */ - overread = pad->src.sizrng[1] - pad->src.offrng[0] < pad->src.bndrng[0]; - range[0] = wide_int_to_tree (sizetype, pad->src.bndrng[0]); - slen = size_zero_node; - } - - if (overread) - { - const opt_code opt = OPT_Wstringop_overread; - if (warning_suppressed_p (exp, opt) - || (srcstr && warning_suppressed_p (srcstr, opt)) - || (pad && pad->src.ref - && warning_suppressed_p (pad->src.ref, opt))) - return false; + if (dest_addr == 0) + { + dest_addr = force_operand (XEXP (dest_mem, 0), target); + dest_addr = convert_memory_address (ptr_mode, dest_addr); + } - location_t loc = EXPR_LOCATION (exp); - const bool read - = mode == access_read_only || mode == access_read_write; - const bool maybe = pad && pad->dst.parmarray; - if (warn_for_access (loc, func, exp, opt, range, slen, false, read, - maybe)) - { - suppress_warning (exp, opt); - if (pad) - pad->src.inform_access (access_read_only); - } - return false; + if (retmode != RETURN_BEGIN && target != const0_rtx) + { + dest_addr = gen_rtx_PLUS (ptr_mode, dest_addr, len_rtx); + /* stpcpy pointer to last byte. */ + if (retmode == RETURN_END_MINUS_ONE) + dest_addr = gen_rtx_MINUS (ptr_mode, dest_addr, const1_rtx); } - return true; + return dest_addr; } -/* A convenience wrapper for check_access above to check access - by a read-only function like puts. */ - -static bool -check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */, - int ost /* = 1 */) +static rtx +expand_builtin_mempcpy_args (tree dest, tree src, tree len, + rtx target, tree orig_exp, memop_ret retmode) { - if (!warn_stringop_overread) - return true; - - if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound))) - bound = fold_convert (size_type_node, bound); - access_data data (exp, access_read_only, NULL_TREE, false, bound, true); - compute_objsize (src, ost, &data.src); - return check_access (exp, /*dstwrite=*/ NULL_TREE, /*maxread=*/ bound, - /*srcstr=*/ src, /*dstsize=*/ NULL_TREE, data.mode, - &data); + return expand_builtin_memory_copy_args (dest, src, len, target, orig_exp, + retmode, false); } -/* If STMT is a call to an allocation function, returns the constant - maximum size of the object allocated by the call represented as - sizetype. If nonnull, sets RNG1[] to the range of the size. - When nonnull, uses RVALS for range information, otherwise gets global - range info. - Returns null when STMT is not a call to a valid allocation function. */ +/* Expand into a movstr instruction, if one is available. Return NULL_RTX if + we failed, the caller should emit a normal call, otherwise try to + get the result in TARGET, if convenient. + Return value is based on RETMODE argument. */ -tree -gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, - range_query * /* = NULL */) +static rtx +expand_movstr (tree dest, tree src, rtx target, memop_ret retmode) { - if (!stmt || !is_gimple_call (stmt)) - return NULL_TREE; - - tree allocfntype; - if (tree fndecl = gimple_call_fndecl (stmt)) - allocfntype = TREE_TYPE (fndecl); - else - allocfntype = gimple_call_fntype (stmt); + class expand_operand ops[3]; + rtx dest_mem; + rtx src_mem; - if (!allocfntype) - return NULL_TREE; + if (!targetm.have_movstr ()) + return NULL_RTX; - unsigned argidx1 = UINT_MAX, argidx2 = UINT_MAX; - tree at = lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (allocfntype)); - if (!at) + dest_mem = get_memory_rtx (dest, NULL); + src_mem = get_memory_rtx (src, NULL); + if (retmode == RETURN_BEGIN) { - if (!gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) - return NULL_TREE; - - argidx1 = 0; + target = force_reg (Pmode, XEXP (dest_mem, 0)); + dest_mem = replace_equiv_address (dest_mem, target); } - unsigned nargs = gimple_call_num_args (stmt); + create_output_operand (&ops[0], + retmode != RETURN_BEGIN ? target : NULL_RTX, Pmode); + create_fixed_operand (&ops[1], dest_mem); + create_fixed_operand (&ops[2], src_mem); + if (!maybe_expand_insn (targetm.code_for_movstr, 3, ops)) + return NULL_RTX; - if (argidx1 == UINT_MAX) + if (retmode != RETURN_BEGIN && target != const0_rtx) { - tree atval = TREE_VALUE (at); - if (!atval) - return NULL_TREE; - - argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; - if (nargs <= argidx1) - return NULL_TREE; - - atval = TREE_CHAIN (atval); - if (atval) + target = ops[0].value; + /* movstr is supposed to set end to the address of the NUL + terminator. If the caller requested a mempcpy-like return value, + adjust it. */ + if (retmode == RETURN_END) { - argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; - if (nargs <= argidx2) - return NULL_TREE; + rtx tem = plus_constant (GET_MODE (target), + gen_lowpart (GET_MODE (target), target), 1); + emit_move_insn (target, force_operand (tem, NULL_RTX)); } } + return target; +} - tree size = gimple_call_arg (stmt, argidx1); - - wide_int rng1_buf[2]; - /* If RNG1 is not set, use the buffer. */ - if (!rng1) - rng1 = rng1_buf; - - /* Use maximum precision to avoid overflow below. */ - const int prec = ADDR_MAX_PRECISION; - - { - tree r[2]; - /* Determine the largest valid range size, including zero. */ - if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) - return NULL_TREE; - rng1[0] = wi::to_wide (r[0], prec); - rng1[1] = wi::to_wide (r[1], prec); - } +/* Do some very basic size validation of a call to the strcpy builtin + given by EXP. Return NULL_RTX to have the built-in expand to a call + to the library function. */ - if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) - return fold_convert (sizetype, size); +static rtx +expand_builtin_strcat (tree exp) +{ + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE) + || !warn_stringop_overflow) + return NULL_RTX; - /* To handle ranges do the math in wide_int and return the product - of the upper bounds as a constant. Ignore anti-ranges. */ - tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node; - wide_int rng2[2]; - { - tree r[2]; - /* As above, use the full non-negative range on failure. */ - if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) - return NULL_TREE; - rng2[0] = wi::to_wide (r[0], prec); - rng2[1] = wi::to_wide (r[1], prec); - } + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); - /* Compute products of both bounds for the caller but return the lesser - of SIZE_MAX and the product of the upper bounds as a constant. */ - rng1[0] = rng1[0] * rng2[0]; - rng1[1] = rng1[1] * rng2[1]; + /* There is no way here to determine the length of the string in + the destination to which the SRC string is being appended so + just diagnose cases when the souce string is longer than + the destination object. */ + access_data data (exp, access_read_write, NULL_TREE, true, + NULL_TREE, true); + const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; + compute_objsize (src, ost, &data.src); + tree destsize = compute_objsize (dest, ost, &data.dst); - const tree size_max = TYPE_MAX_VALUE (sizetype); - if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec))) - { - rng1[1] = wi::to_wide (size_max, prec); - return size_max; - } + check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE, + src, destsize, data.mode, &data); - return wide_int_to_tree (sizetype, rng1[1]); + return NULL_RTX; } -/* For an access to an object referenced to by the function parameter PTR - of pointer type, and set RNG[] to the range of sizes of the object - obtainedfrom the attribute access specification for the current function. - Set STATIC_ARRAY if the array parameter has been declared [static]. - Return the function parameter on success and null otherwise. */ - -tree -gimple_parm_array_size (tree ptr, wide_int rng[2], - bool *static_array /* = NULL */) -{ - /* For a function argument try to determine the byte size of the array - from the current function declaratation (e.g., attribute access or - related). */ - tree var = SSA_NAME_VAR (ptr); - if (TREE_CODE (var) != PARM_DECL) - return NULL_TREE; +/* Expand expression EXP, which is a call to the strcpy builtin. Return + NULL_RTX if we failed the caller should emit a normal call, otherwise + try to get the result in TARGET, if convenient (and in mode MODE if that's + convenient). */ - const unsigned prec = TYPE_PRECISION (sizetype); +static rtx +expand_builtin_strcpy (tree exp, rtx target) +{ + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + return NULL_RTX; - rdwr_map rdwr_idx; - attr_access *access = get_parm_access (rdwr_idx, var); - if (!access) - return NULL_TREE; + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); - if (access->sizarg != UINT_MAX) + if (warn_stringop_overflow) { - /* TODO: Try to extract the range from the argument based on - those of subsequent assertions or based on known calls to - the current function. */ - return NULL_TREE; + access_data data (exp, access_read_write, NULL_TREE, true, + NULL_TREE, true); + const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; + compute_objsize (src, ost, &data.src); + tree dstsize = compute_objsize (dest, ost, &data.dst); + check_access (exp, /*dstwrite=*/ NULL_TREE, + /*maxread=*/ NULL_TREE, /*srcstr=*/ src, + dstsize, data.mode, &data); } - if (!access->minsize) - return NULL_TREE; - - /* Only consider ordinary array bound at level 2 (or above if it's - ever added). */ - if (warn_array_parameter < 2 && !access->static_p) - return NULL_TREE; - - if (static_array) - *static_array = access->static_p; - - rng[0] = wi::zero (prec); - rng[1] = wi::uhwi (access->minsize, prec); - /* Multiply the array bound encoded in the attribute by the size - of what the pointer argument to which it decays points to. */ - tree eltype = TREE_TYPE (TREE_TYPE (ptr)); - tree size = TYPE_SIZE_UNIT (eltype); - if (!size || TREE_CODE (size) != INTEGER_CST) - return NULL_TREE; + if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target)) + { + /* Check to see if the argument was declared attribute nonstring + and if so, issue a warning since at this point it's not known + to be nul-terminated. */ + tree fndecl = get_callee_fndecl (exp); + maybe_warn_nonstring_arg (fndecl, exp); + return ret; + } - rng[1] *= wi::to_wide (size, prec); - return var; + return NULL_RTX; } -/* Wrapper around the wide_int overload of get_range that accepts - offset_int instead. For middle end expressions returns the same - result. For a subset of nonconstamt expressions emitted by the front - end determines a more precise range than would be possible otherwise. */ +/* Helper function to do the actual work for expand_builtin_strcpy. The + arguments to the builtin_strcpy call DEST and SRC are broken out + so that this can also be called without constructing an actual CALL_EXPR. + The other arguments and return value are the same as for + expand_builtin_strcpy. */ -static bool -get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals) +static rtx +expand_builtin_strcpy_args (tree exp, tree dest, tree src, rtx target) { - offset_int add = 0; - if (TREE_CODE (x) == PLUS_EXPR) + /* Detect strcpy calls with unterminated arrays.. */ + tree size; + bool exact; + if (tree nonstr = unterminated_array (src, &size, &exact)) { - /* Handle constant offsets in pointer addition expressions seen - n the front end IL. */ - tree op = TREE_OPERAND (x, 1); - if (TREE_CODE (op) == INTEGER_CST) - { - op = fold_convert (signed_type_for (TREE_TYPE (op)), op); - add = wi::to_offset (op); - x = TREE_OPERAND (x, 0); - } + /* NONSTR refers to the non-nul terminated constant array. */ + warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, nonstr, + size, exact); + return NULL_RTX; } - if (TREE_CODE (x) == NOP_EXPR) - /* Also handle conversions to sizetype seen in the front end IL. */ - x = TREE_OPERAND (x, 0); + return expand_movstr (dest, src, target, /*retmode=*/ RETURN_BEGIN); +} - tree type = TREE_TYPE (x); - if (!INTEGRAL_TYPE_P (type) && !POINTER_TYPE_P (type)) - return false; +/* Expand a call EXP to the stpcpy builtin. + Return NULL_RTX if we failed the caller should emit a normal call, + otherwise try to get the result in TARGET, if convenient (and in + mode MODE if that's convenient). */ - if (TREE_CODE (x) != INTEGER_CST - && TREE_CODE (x) != SSA_NAME) - { - if (TYPE_UNSIGNED (type) - && TYPE_PRECISION (type) == TYPE_PRECISION (sizetype)) - type = signed_type_for (type); +static rtx +expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode) +{ + tree dst, src; + location_t loc = EXPR_LOCATION (exp); - r[0] = wi::to_offset (TYPE_MIN_VALUE (type)) + add; - r[1] = wi::to_offset (TYPE_MAX_VALUE (type)) + add; - return x; + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + return NULL_RTX; + + dst = CALL_EXPR_ARG (exp, 0); + src = CALL_EXPR_ARG (exp, 1); + + if (warn_stringop_overflow) + { + access_data data (exp, access_read_write); + tree destsize = compute_objsize (dst, warn_stringop_overflow - 1, + &data.dst); + check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE, + src, destsize, data.mode, &data); } - wide_int wr[2]; - if (!get_range (x, stmt, wr, rvals)) - return false; + /* If return value is ignored, transform stpcpy into strcpy. */ + if (target == const0_rtx && builtin_decl_implicit (BUILT_IN_STRCPY)) + { + tree fn = builtin_decl_implicit (BUILT_IN_STRCPY); + tree result = build_call_nofold_loc (loc, fn, 2, dst, src); + return expand_expr (result, target, mode, EXPAND_NORMAL); + } + else + { + tree len, lenp1; + rtx ret; - signop sgn = SIGNED; - /* Only convert signed integers or unsigned sizetype to a signed - offset and avoid converting large positive values in narrower - types to negative offsets. */ - if (TYPE_UNSIGNED (type) - && wr[0].get_precision () < TYPE_PRECISION (sizetype)) - sgn = UNSIGNED; + /* Ensure we get an actual string whose length can be evaluated at + compile-time, not an expression containing a string. This is + because the latter will potentially produce pessimized code + when used to produce the return value. */ + c_strlen_data lendata = { }; + if (!c_getstr (src) + || !(len = c_strlen (src, 0, &lendata, 1))) + return expand_movstr (dst, src, target, + /*retmode=*/ RETURN_END_MINUS_ONE); - r[0] = offset_int::from (wr[0], sgn); - r[1] = offset_int::from (wr[1], sgn); - return true; -} + if (lendata.decl) + warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, lendata.decl); -/* Return the argument that the call STMT to a built-in function returns - or null if it doesn't. On success, set OFFRNG[] to the range of offsets - from the argument reflected in the value returned by the built-in if it - can be determined, otherwise to 0 and HWI_M1U respectively. Set - *PAST_END for functions like mempcpy that might return a past the end - pointer (most functions return a dereferenceable pointer to an existing - element of an array). */ + lenp1 = size_binop_loc (loc, PLUS_EXPR, len, ssize_int (1)); + ret = expand_builtin_mempcpy_args (dst, src, lenp1, + target, exp, + /*retmode=*/ RETURN_END_MINUS_ONE); -static tree -gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end, - range_query *rvals) -{ - /* Clear and set below for the rare function(s) that might return - a past-the-end pointer. */ - *past_end = false; + if (ret) + return ret; - { - /* Check for attribute fn spec to see if the function returns one - of its arguments. */ - attr_fnspec fnspec = gimple_call_fnspec (as_a (stmt)); - unsigned int argno; - if (fnspec.returns_arg (&argno)) - { - /* Functions return the first argument (not a range). */ - offrng[0] = offrng[1] = 0; - return gimple_call_arg (stmt, argno); - } - } + if (TREE_CODE (len) == INTEGER_CST) + { + rtx len_rtx = expand_normal (len); - if (gimple_call_num_args (stmt) < 1) - return NULL_TREE; + if (CONST_INT_P (len_rtx)) + { + ret = expand_builtin_strcpy_args (exp, dst, src, target); - tree fn = gimple_call_fndecl (stmt); - if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) - { - /* See if this is a call to placement new. */ - if (!fn - || !DECL_IS_OPERATOR_NEW_P (fn) - || DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fn)) - return NULL_TREE; + if (ret) + { + if (! target) + { + if (mode != VOIDmode) + target = gen_reg_rtx (mode); + else + target = gen_reg_rtx (GET_MODE (ret)); + } + if (GET_MODE (target) != GET_MODE (ret)) + ret = gen_lowpart (GET_MODE (target), ret); - /* Check the mangling, keeping in mind that operator new takes - a size_t which could be unsigned int or unsigned long. */ - tree fname = DECL_ASSEMBLER_NAME (fn); - if (!id_equal (fname, "_ZnwjPv") // ordinary form - && !id_equal (fname, "_ZnwmPv") // ordinary form - && !id_equal (fname, "_ZnajPv") // array form - && !id_equal (fname, "_ZnamPv")) // array form - return NULL_TREE; + ret = plus_constant (GET_MODE (ret), ret, INTVAL (len_rtx)); + ret = emit_move_insn (target, force_operand (ret, NULL_RTX)); + gcc_assert (ret); - if (gimple_call_num_args (stmt) != 2) - return NULL_TREE; + return target; + } + } + } - /* Allocation functions return a pointer to the beginning. */ - offrng[0] = offrng[1] = 0; - return gimple_call_arg (stmt, 1); + return expand_movstr (dst, src, target, + /*retmode=*/ RETURN_END_MINUS_ONE); } +} - switch (DECL_FUNCTION_CODE (fn)) - { - case BUILT_IN_MEMCPY: - case BUILT_IN_MEMCPY_CHK: - case BUILT_IN_MEMMOVE: - case BUILT_IN_MEMMOVE_CHK: - case BUILT_IN_MEMSET: - case BUILT_IN_STRCAT: - case BUILT_IN_STRCAT_CHK: - case BUILT_IN_STRCPY: - case BUILT_IN_STRCPY_CHK: - case BUILT_IN_STRNCAT: - case BUILT_IN_STRNCAT_CHK: - case BUILT_IN_STRNCPY: - case BUILT_IN_STRNCPY_CHK: - /* Functions return the first argument (not a range). */ - offrng[0] = offrng[1] = 0; - return gimple_call_arg (stmt, 0); +/* Expand a call EXP to the stpcpy builtin and diagnose uses of nonstring + arguments while being careful to avoid duplicate warnings (which could + be issued if the expander were to expand the call, resulting in it + being emitted in expand_call(). */ - case BUILT_IN_MEMPCPY: - case BUILT_IN_MEMPCPY_CHK: - { - /* The returned pointer is in a range constrained by the smaller - of the upper bound of the size argument and the source object - size. */ - offrng[0] = 0; - offrng[1] = HOST_WIDE_INT_M1U; - tree off = gimple_call_arg (stmt, 2); - bool off_valid = get_offset_range (off, stmt, offrng, rvals); - if (!off_valid || offrng[0] != offrng[1]) - { - /* If the offset is either indeterminate or in some range, - try to constrain its upper bound to at most the size - of the source object. */ - access_ref aref; - tree src = gimple_call_arg (stmt, 1); - if (compute_objsize (src, 1, &aref, rvals) - && aref.sizrng[1] < offrng[1]) - offrng[1] = aref.sizrng[1]; - } +static rtx +expand_builtin_stpcpy (tree exp, rtx target, machine_mode mode) +{ + if (rtx ret = expand_builtin_stpcpy_1 (exp, target, mode)) + { + /* The call has been successfully expanded. Check for nonstring + arguments and issue warnings as appropriate. */ + maybe_warn_nonstring_arg (get_callee_fndecl (exp), exp); + return ret; + } - /* Mempcpy may return a past-the-end pointer. */ - *past_end = true; - return gimple_call_arg (stmt, 0); - } + return NULL_RTX; +} - case BUILT_IN_MEMCHR: - { - tree off = gimple_call_arg (stmt, 2); - if (get_offset_range (off, stmt, offrng, rvals)) - offrng[1] -= 1; - else - offrng[1] = HOST_WIDE_INT_M1U; +/* Check a call EXP to the stpncpy built-in for validity. + Return NULL_RTX on both success and failure. */ - offrng[0] = 0; - return gimple_call_arg (stmt, 0); - } +static rtx +expand_builtin_stpncpy (tree exp, rtx) +{ + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) + || !warn_stringop_overflow) + return NULL_RTX; - case BUILT_IN_STRCHR: - case BUILT_IN_STRRCHR: - case BUILT_IN_STRSTR: - offrng[0] = 0; - offrng[1] = HOST_WIDE_INT_M1U; - return gimple_call_arg (stmt, 0); + /* The source and destination of the call. */ + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); - case BUILT_IN_STPCPY: - case BUILT_IN_STPCPY_CHK: - { - access_ref aref; - tree src = gimple_call_arg (stmt, 1); - if (compute_objsize (src, 1, &aref, rvals)) - offrng[1] = aref.sizrng[1] - 1; - else - offrng[1] = HOST_WIDE_INT_M1U; - - offrng[0] = 0; - return gimple_call_arg (stmt, 0); - } + /* The exact number of bytes to write (not the maximum). */ + tree len = CALL_EXPR_ARG (exp, 2); + access_data data (exp, access_read_write); + /* The size of the destination object. */ + tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); + check_access (exp, len, /*maxread=*/len, src, destsize, data.mode, &data); + return NULL_RTX; +} - case BUILT_IN_STPNCPY: - case BUILT_IN_STPNCPY_CHK: - { - /* The returned pointer is in a range between the first argument - and it plus the smaller of the upper bound of the size argument - and the source object size. */ - offrng[1] = HOST_WIDE_INT_M1U; - tree off = gimple_call_arg (stmt, 2); - if (!get_offset_range (off, stmt, offrng, rvals) - || offrng[0] != offrng[1]) - { - /* If the offset is either indeterminate or in some range, - try to constrain its upper bound to at most the size - of the source object. */ - access_ref aref; - tree src = gimple_call_arg (stmt, 1); - if (compute_objsize (src, 1, &aref, rvals) - && aref.sizrng[1] < offrng[1]) - offrng[1] = aref.sizrng[1]; - } +/* Callback routine for store_by_pieces. Read GET_MODE_BITSIZE (MODE) + bytes from constant string DATA + OFFSET and return it as target + constant. */ - /* When the source is the empty string the returned pointer is - a copy of the argument. Otherwise stpcpy can also return - a past-the-end pointer. */ - offrng[0] = 0; - *past_end = true; - return gimple_call_arg (stmt, 0); - } +rtx +builtin_strncpy_read_str (void *data, void *, HOST_WIDE_INT offset, + scalar_int_mode mode) +{ + const char *str = (const char *) data; - default: - break; - } + if ((unsigned HOST_WIDE_INT) offset > strlen (str)) + return const0_rtx; - return NULL_TREE; + return c_readstr (str + offset, mode); } -/* A helper of compute_objsize_r() to determine the size from an assignment - statement STMT with the RHS of either MIN_EXPR or MAX_EXPR. */ +/* Helper to check the sizes of sequences and the destination of calls + to __builtin_strncat and __builtin___strncat_chk. Returns true on + success (no overflow or invalid sizes), false otherwise. */ static bool -handle_min_max_size (gimple *stmt, int ostype, access_ref *pref, - ssa_name_limit_t &snlim, pointer_query *qry) +check_strncat_sizes (tree exp, tree objsize) { - tree_code code = gimple_assign_rhs_code (stmt); + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree maxread = CALL_EXPR_ARG (exp, 2); - tree ptr = gimple_assign_rhs1 (stmt); + /* Try to determine the range of lengths that the source expression + refers to. */ + c_strlen_data lendata = { }; + get_range_strlen (src, &lendata, /* eltsize = */ 1); - /* In a valid MAX_/MIN_EXPR both operands must refer to the same array. - Determine the size/offset of each and use the one with more or less - space remaining, respectively. If either fails, use the information - determined from the other instead, adjusted up or down as appropriate - for the expression. */ - access_ref aref[2] = { *pref, *pref }; - if (!compute_objsize_r (ptr, ostype, &aref[0], snlim, qry)) - { - aref[0].base0 = false; - aref[0].offrng[0] = aref[0].offrng[1] = 0; - aref[0].add_max_offset (); - aref[0].set_max_size_range (); - } + /* Try to verify that the destination is big enough for the shortest + string. */ - ptr = gimple_assign_rhs2 (stmt); - if (!compute_objsize_r (ptr, ostype, &aref[1], snlim, qry)) + access_data data (exp, access_read_write, maxread, true); + if (!objsize && warn_stringop_overflow) { - aref[1].base0 = false; - aref[1].offrng[0] = aref[1].offrng[1] = 0; - aref[1].add_max_offset (); - aref[1].set_max_size_range (); + /* If it hasn't been provided by __strncat_chk, try to determine + the size of the destination object into which the source is + being copied. */ + objsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); } - if (!aref[0].ref && !aref[1].ref) - /* Fail if the identity of neither argument could be determined. */ - return false; + /* Add one for the terminating nul. */ + tree srclen = (lendata.minlen + ? fold_build2 (PLUS_EXPR, size_type_node, lendata.minlen, + size_one_node) + : NULL_TREE); - bool i0 = false; - if (aref[0].ref && aref[0].base0) + /* The strncat function copies at most MAXREAD bytes and always appends + the terminating nul so the specified upper bound should never be equal + to (or greater than) the size of the destination. */ + if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize) + && tree_int_cst_equal (objsize, maxread)) { - if (aref[1].ref && aref[1].base0) - { - /* If the object referenced by both arguments has been determined - set *PREF to the one with more or less space remainng, whichever - is appopriate for CODE. - TODO: Indicate when the objects are distinct so it can be - diagnosed. */ - i0 = code == MAX_EXPR; - const bool i1 = !i0; - - if (aref[i0].size_remaining () < aref[i1].size_remaining ()) - *pref = aref[i1]; - else - *pref = aref[i0]; - return true; - } + location_t loc = EXPR_LOCATION (exp); + warning_at (loc, OPT_Wstringop_overflow_, + "%qD specified bound %E equals destination size", + get_callee_fndecl (exp), maxread); - /* If only the object referenced by one of the arguments could be - determined, use it and... */ - *pref = aref[0]; - i0 = true; + return false; } - else - *pref = aref[1]; - const bool i1 = !i0; - /* ...see if the offset obtained from the other pointer can be used - to tighten up the bound on the offset obtained from the first. */ - if ((code == MAX_EXPR && aref[i1].offrng[1] < aref[i0].offrng[0]) - || (code == MIN_EXPR && aref[i0].offrng[0] < aref[i1].offrng[1])) - { - pref->offrng[0] = aref[i0].offrng[0]; - pref->offrng[1] = aref[i0].offrng[1]; - } - return true; + if (!srclen + || (maxread && tree_fits_uhwi_p (maxread) + && tree_fits_uhwi_p (srclen) + && tree_int_cst_lt (maxread, srclen))) + srclen = maxread; + + /* The number of bytes to write is LEN but check_access will alsoa + check SRCLEN if LEN's value isn't known. */ + return check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen, + objsize, data.mode, &data); } -/* A helper of compute_objsize_r() to determine the size from ARRAY_REF - AREF. ADDR is true if PTR is the operand of ADDR_EXPR. Return true - on success and false on failure. */ +/* Similar to expand_builtin_strcat, do some very basic size validation + of a call to the strcpy builtin given by EXP. Return NULL_RTX to have + the built-in expand to a call to the library function. */ -static bool -handle_array_ref (tree aref, bool addr, int ostype, access_ref *pref, - ssa_name_limit_t &snlim, pointer_query *qry) +static rtx +expand_builtin_strncat (tree exp, rtx) { - gcc_assert (TREE_CODE (aref) == ARRAY_REF); + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) + || !warn_stringop_overflow) + return NULL_RTX; - ++pref->deref; + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + /* The upper bound on the number of bytes to write. */ + tree maxread = CALL_EXPR_ARG (exp, 2); - tree arefop = TREE_OPERAND (aref, 0); - tree reftype = TREE_TYPE (arefop); - if (!addr && TREE_CODE (TREE_TYPE (reftype)) == POINTER_TYPE) - /* Avoid arrays of pointers. FIXME: Hande pointers to arrays - of known bound. */ - return false; + /* Detect unterminated source (only). */ + if (!check_nul_terminated_array (exp, src, maxread)) + return NULL_RTX; - if (!compute_objsize_r (arefop, ostype, pref, snlim, qry)) - return false; + /* The length of the source sequence. */ + tree slen = c_strlen (src, 1); - offset_int orng[2]; - tree off = pref->eval (TREE_OPERAND (aref, 1)); - range_query *const rvals = qry ? qry->rvals : NULL; - if (!get_offset_range (off, NULL, orng, rvals)) + /* Try to determine the range of lengths that the source expression + refers to. Since the lengths are only used for warning and not + for code generation disable strict mode below. */ + tree maxlen = slen; + if (!maxlen) { - /* Set ORNG to the maximum offset representable in ptrdiff_t. */ - orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - orng[0] = -orng[1] - 1; + c_strlen_data lendata = { }; + get_range_strlen (src, &lendata, /* eltsize = */ 1); + maxlen = lendata.maxbound; } - /* Convert the array index range determined above to a byte - offset. */ - tree lowbnd = array_ref_low_bound (aref); - if (!integer_zerop (lowbnd) && tree_fits_uhwi_p (lowbnd)) - { - /* Adjust the index by the low bound of the array domain - (normally zero but 1 in Fortran). */ - unsigned HOST_WIDE_INT lb = tree_to_uhwi (lowbnd); - orng[0] -= lb; - orng[1] -= lb; - } + access_data data (exp, access_read_write); + /* Try to verify that the destination is big enough for the shortest + string. First try to determine the size of the destination object + into which the source is being copied. */ + tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); + + /* Add one for the terminating nul. */ + tree srclen = (maxlen + ? fold_build2 (PLUS_EXPR, size_type_node, maxlen, + size_one_node) + : NULL_TREE); - tree eltype = TREE_TYPE (aref); - tree tpsize = TYPE_SIZE_UNIT (eltype); - if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST) + /* The strncat function copies at most MAXREAD bytes and always appends + the terminating nul so the specified upper bound should never be equal + to (or greater than) the size of the destination. */ + if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize) + && tree_int_cst_equal (destsize, maxread)) { - pref->add_max_offset (); - return true; + location_t loc = EXPR_LOCATION (exp); + warning_at (loc, OPT_Wstringop_overflow_, + "%qD specified bound %E equals destination size", + get_callee_fndecl (exp), maxread); + + return NULL_RTX; } - offset_int sz = wi::to_offset (tpsize); - orng[0] *= sz; - orng[1] *= sz; + if (!srclen + || (maxread && tree_fits_uhwi_p (maxread) + && tree_fits_uhwi_p (srclen) + && tree_int_cst_lt (maxread, srclen))) + srclen = maxread; - if (ostype && TREE_CODE (eltype) == ARRAY_TYPE) - { - /* Except for the permissive raw memory functions which use - the size of the whole object determined above, use the size - of the referenced array. Because the overall offset is from - the beginning of the complete array object add this overall - offset to the size of array. */ - offset_int sizrng[2] = - { - pref->offrng[0] + orng[0] + sz, - pref->offrng[1] + orng[1] + sz - }; - if (sizrng[1] < sizrng[0]) - std::swap (sizrng[0], sizrng[1]); - if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0]) - pref->sizrng[0] = sizrng[0]; - if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1]) - pref->sizrng[1] = sizrng[1]; - } - - pref->add_offset (orng[0], orng[1]); - return true; + check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen, + destsize, data.mode, &data); + return NULL_RTX; } -/* A helper of compute_objsize_r() to determine the size from MEM_REF - MREF. Return true on success and false on failure. */ +/* Expand expression EXP, which is a call to the strncpy builtin. Return + NULL_RTX if we failed the caller should emit a normal call. */ -static bool -handle_mem_ref (tree mref, int ostype, access_ref *pref, - ssa_name_limit_t &snlim, pointer_query *qry) +static rtx +expand_builtin_strncpy (tree exp, rtx target) { - gcc_assert (TREE_CODE (mref) == MEM_REF); + location_t loc = EXPR_LOCATION (exp); + + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + /* The number of bytes to write (not the maximum). */ + tree len = CALL_EXPR_ARG (exp, 2); - ++pref->deref; + /* The length of the source sequence. */ + tree slen = c_strlen (src, 1); - if (VECTOR_TYPE_P (TREE_TYPE (mref))) + if (warn_stringop_overflow) { - /* Hack: Handle MEM_REFs of vector types as those to complete - objects; those may be synthesized from multiple assignments - to consecutive data members (see PR 93200 and 96963). - FIXME: Vectorized assignments should only be present after - vectorization so this hack is only necessary after it has - run and could be avoided in calls from prior passes (e.g., - tree-ssa-strlen.c). - FIXME: Deal with this more generally, e.g., by marking up - such MEM_REFs at the time they're created. */ - ostype = 0; + access_data data (exp, access_read_write, len, true, len, true); + const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; + compute_objsize (src, ost, &data.src); + tree dstsize = compute_objsize (dest, ost, &data.dst); + /* The number of bytes to write is LEN but check_access will also + check SLEN if LEN's value isn't known. */ + check_access (exp, /*dstwrite=*/len, + /*maxread=*/len, src, dstsize, data.mode, &data); } - tree mrefop = TREE_OPERAND (mref, 0); - if (!compute_objsize_r (mrefop, ostype, pref, snlim, qry)) - return false; + /* We must be passed a constant len and src parameter. */ + if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen)) + return NULL_RTX; - offset_int orng[2]; - tree off = pref->eval (TREE_OPERAND (mref, 1)); - range_query *const rvals = qry ? qry->rvals : NULL; - if (!get_offset_range (off, NULL, orng, rvals)) + slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1)); + + /* We're required to pad with trailing zeros if the requested + len is greater than strlen(s2)+1. In that case try to + use store_by_pieces, if it fails, punt. */ + if (tree_int_cst_lt (slen, len)) { - /* Set ORNG to the maximum offset representable in ptrdiff_t. */ - orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - orng[0] = -orng[1] - 1; + unsigned int dest_align = get_pointer_alignment (dest); + const char *p = c_getstr (src); + rtx dest_mem; + + if (!p || dest_align == 0 || !tree_fits_uhwi_p (len) + || !can_store_by_pieces (tree_to_uhwi (len), + builtin_strncpy_read_str, + CONST_CAST (char *, p), + dest_align, false)) + return NULL_RTX; + + dest_mem = get_memory_rtx (dest, len); + store_by_pieces (dest_mem, tree_to_uhwi (len), + builtin_strncpy_read_str, + CONST_CAST (char *, p), dest_align, false, + RETURN_BEGIN); + dest_mem = force_operand (XEXP (dest_mem, 0), target); + dest_mem = convert_memory_address (ptr_mode, dest_mem); + return dest_mem; } - pref->add_offset (orng[0], orng[1]); - return true; + return NULL_RTX; } -/* Helper to compute the size of the object referenced by the PTR - expression which must have pointer type, using Object Size type - OSTYPE (only the least significant 2 bits are used). - On success, sets PREF->REF to the DECL of the referenced object - if it's unique, otherwise to null, PREF->OFFRNG to the range of - offsets into it, and PREF->SIZRNG to the range of sizes of - the object(s). - SNLIM is used to avoid visiting the same PHI operand multiple - times, and, when nonnull, RVALS to determine range information. - Returns true on success, false when a meaningful size (or range) - cannot be determined. - - The function is intended for diagnostics and should not be used - to influence code generation or optimization. */ +/* Callback routine for store_by_pieces. Read GET_MODE_BITSIZE (MODE) + bytes from constant string DATA + OFFSET and return it as target + constant. If PREV isn't nullptr, it has the RTL info from the + previous iteration. */ -static bool -compute_objsize_r (tree ptr, int ostype, access_ref *pref, - ssa_name_limit_t &snlim, pointer_query *qry) +rtx +builtin_memset_read_str (void *data, void *prevp, + HOST_WIDE_INT offset ATTRIBUTE_UNUSED, + scalar_int_mode mode) { - STRIP_NOPS (ptr); - - const bool addr = TREE_CODE (ptr) == ADDR_EXPR; - if (addr) + by_pieces_prev *prev = (by_pieces_prev *) prevp; + if (prev != nullptr && prev->data != nullptr) { - --pref->deref; - ptr = TREE_OPERAND (ptr, 0); + /* Use the previous data in the same mode. */ + if (prev->mode == mode) + return prev->data; } - if (DECL_P (ptr)) - { - pref->ref = ptr; + const char *c = (const char *) data; + char *p = XALLOCAVEC (char, GET_MODE_SIZE (mode)); - if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr))) - { - /* Set the maximum size if the reference is to the pointer - itself (as opposed to what it points to), and clear - BASE0 since the offset isn't necessarily zero-based. */ - pref->set_max_size_range (); - pref->base0 = false; - return true; - } + memset (p, *c, GET_MODE_SIZE (mode)); - if (tree size = decl_init_size (ptr, false)) - if (TREE_CODE (size) == INTEGER_CST) - { - pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); - return true; - } + return c_readstr (p, mode); +} - pref->set_max_size_range (); - return true; - } +/* Callback routine for store_by_pieces. Return the RTL of a register + containing GET_MODE_SIZE (MODE) consecutive copies of the unsigned + char value given in the RTL register data. For example, if mode is + 4 bytes wide, return the RTL for 0x01010101*data. If PREV isn't + nullptr, it has the RTL info from the previous iteration. */ - const tree_code code = TREE_CODE (ptr); - range_query *const rvals = qry ? qry->rvals : NULL; +static rtx +builtin_memset_gen_str (void *data, void *prevp, + HOST_WIDE_INT offset ATTRIBUTE_UNUSED, + scalar_int_mode mode) +{ + rtx target, coeff; + size_t size; + char *p; - if (code == BIT_FIELD_REF) + by_pieces_prev *prev = (by_pieces_prev *) prevp; + if (prev != nullptr && prev->data != nullptr) { - tree ref = TREE_OPERAND (ptr, 0); - if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) - return false; + /* Use the previous data in the same mode. */ + if (prev->mode == mode) + return prev->data; - offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2))); - pref->add_offset (off / BITS_PER_UNIT); - return true; + target = simplify_gen_subreg (mode, prev->data, prev->mode, 0); + if (target != nullptr) + return target; } - if (code == COMPONENT_REF) - { - tree ref = TREE_OPERAND (ptr, 0); - if (TREE_CODE (TREE_TYPE (ref)) == UNION_TYPE) - /* In accesses through union types consider the entire unions - rather than just their members. */ - ostype = 0; - tree field = TREE_OPERAND (ptr, 1); + size = GET_MODE_SIZE (mode); + if (size == 1) + return (rtx) data; - if (ostype == 0) - { - /* In OSTYPE zero (for raw memory functions like memcpy), use - the maximum size instead if the identity of the enclosing - object cannot be determined. */ - if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) - return false; - - /* Otherwise, use the size of the enclosing object and add - the offset of the member to the offset computed so far. */ - tree offset = byte_position (field); - if (TREE_CODE (offset) == INTEGER_CST) - pref->add_offset (wi::to_offset (offset)); - else - pref->add_max_offset (); - - if (!pref->ref) - /* REF may have been already set to an SSA_NAME earlier - to provide better context for diagnostics. In that case, - leave it unchanged. */ - pref->ref = ref; - return true; - } + p = XALLOCAVEC (char, size); + memset (p, 1, size); + coeff = c_readstr (p, mode); - pref->ref = field; + target = convert_to_mode (mode, (rtx) data, 1); + target = expand_mult (mode, target, coeff, NULL_RTX, 1); + return force_reg (mode, target); +} - if (!addr && POINTER_TYPE_P (TREE_TYPE (field))) - { - /* Set maximum size if the reference is to the pointer member - itself (as opposed to what it points to). */ - pref->set_max_size_range (); - return true; - } +/* Expand expression EXP, which is a call to the memset builtin. Return + NULL_RTX if we failed the caller should emit a normal call, otherwise + try to get the result in TARGET, if convenient (and in mode MODE if that's + convenient). */ - /* SAM is set for array members that might need special treatment. */ - special_array_member sam; - tree size = component_ref_size (ptr, &sam); - if (sam == special_array_member::int_0) - pref->sizrng[0] = pref->sizrng[1] = 0; - else if (!pref->trail1special && sam == special_array_member::trail_1) - pref->sizrng[0] = pref->sizrng[1] = 1; - else if (size && TREE_CODE (size) == INTEGER_CST) - pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); - else - { - /* When the size of the member is unknown it's either a flexible - array member or a trailing special array member (either zero - length or one-element). Set the size to the maximum minus - the constant size of the type. */ - pref->sizrng[0] = 0; - pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref))) - if (TREE_CODE (recsize) == INTEGER_CST) - pref->sizrng[1] -= wi::to_offset (recsize); - } - return true; - } +static rtx +expand_builtin_memset (tree exp, rtx target, machine_mode mode) +{ + if (!validate_arglist (exp, + POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - if (code == ARRAY_REF) - return handle_array_ref (ptr, addr, ostype, pref, snlim, qry); + tree dest = CALL_EXPR_ARG (exp, 0); + tree val = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); - if (code == MEM_REF) - return handle_mem_ref (ptr, ostype, pref, snlim, qry); + check_memop_access (exp, dest, NULL_TREE, len); - if (code == TARGET_MEM_REF) - { - tree ref = TREE_OPERAND (ptr, 0); - if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) - return false; + return expand_builtin_memset_args (dest, val, len, target, mode, exp); +} - /* TODO: Handle remaining operands. Until then, add maximum offset. */ - pref->ref = ptr; - pref->add_max_offset (); - return true; - } +/* Try to store VAL (or, if NULL_RTX, VALC) in LEN bytes starting at TO. + Return TRUE if successful, FALSE otherwise. TO is assumed to be + aligned at an ALIGN-bits boundary. LEN must be a multiple of + 1<set_max_size_range (); - else - pref->sizrng[0] = pref->sizrng[1] = 0; - pref->ref = ptr; + The strategy is to issue one store_by_pieces for each power of two, + from most to least significant, guarded by a test on whether there + are at least that many bytes left to copy in LEN. - return true; - } + ??? Should we skip some powers of two in favor of loops? Maybe start + at the max of TO/LEN/word alignment, at least when optimizing for + size, instead of ensuring O(log len) dynamic compares? */ - if (code == STRING_CST) - { - pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr); - pref->ref = ptr; - return true; - } +bool +try_store_by_multiple_pieces (rtx to, rtx len, unsigned int ctz_len, + unsigned HOST_WIDE_INT min_len, + unsigned HOST_WIDE_INT max_len, + rtx val, char valc, unsigned int align) +{ + int max_bits = floor_log2 (max_len); + int min_bits = floor_log2 (min_len); + int sctz_len = ctz_len; - if (code == POINTER_PLUS_EXPR) - { - tree ref = TREE_OPERAND (ptr, 0); - if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) - return false; + gcc_checking_assert (sctz_len >= 0); - /* Clear DEREF since the offset is being applied to the target - of the dereference. */ - pref->deref = 0; + if (val) + valc = 1; - offset_int orng[2]; - tree off = pref->eval (TREE_OPERAND (ptr, 1)); - if (get_offset_range (off, NULL, orng, rvals)) - pref->add_offset (orng[0], orng[1]); - else - pref->add_max_offset (); - return true; - } + /* Bits more significant than TST_BITS are part of the shared prefix + in the binary representation of both min_len and max_len. Since + they're identical, we don't need to test them in the loop. */ + int tst_bits = (max_bits != min_bits ? max_bits + : floor_log2 (max_len ^ min_len)); - if (code == VIEW_CONVERT_EXPR) + /* Check whether it's profitable to start by storing a fixed BLKSIZE + bytes, to lower max_bits. In the unlikely case of a constant LEN + (implied by identical MAX_LEN and MIN_LEN), we want to issue a + single store_by_pieces, but otherwise, select the minimum multiple + of the ALIGN (in bytes) and of the MCD of the possible LENs, that + brings MAX_LEN below TST_BITS, if that's lower than min_len. */ + unsigned HOST_WIDE_INT blksize; + if (max_len > min_len) { - ptr = TREE_OPERAND (ptr, 0); - return compute_objsize_r (ptr, ostype, pref, snlim, qry); + unsigned HOST_WIDE_INT alrng = MAX (HOST_WIDE_INT_1U << ctz_len, + align / BITS_PER_UNIT); + blksize = max_len - (HOST_WIDE_INT_1U << tst_bits) + alrng; + blksize &= ~(alrng - 1); } - - if (code == SSA_NAME) + else if (max_len == min_len) + blksize = max_len; + else + gcc_unreachable (); + if (min_len >= blksize) { - if (!snlim.next ()) - return false; + min_len -= blksize; + min_bits = floor_log2 (min_len); + max_len -= blksize; + max_bits = floor_log2 (max_len); - /* Only process an SSA_NAME if the recursion limit has not yet - been reached. */ - if (qry) - { - if (++qry->depth) - qry->max_depth = qry->depth; - if (const access_ref *cache_ref = qry->get_ref (ptr)) - { - /* If the pointer is in the cache set *PREF to what it refers - to and return success. */ - *pref = *cache_ref; - return true; - } - } + tst_bits = (max_bits != min_bits ? max_bits + : floor_log2 (max_len ^ min_len)); + } + else + blksize = 0; - gimple *stmt = SSA_NAME_DEF_STMT (ptr); - if (is_gimple_call (stmt)) - { - /* If STMT is a call to an allocation function get the size - from its argument(s). If successful, also set *PREF->REF - to PTR for the caller to include in diagnostics. */ - wide_int wr[2]; - if (gimple_call_alloc_size (stmt, wr, rvals)) - { - pref->ref = ptr; - pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); - pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); - /* Constrain both bounds to a valid size. */ - offset_int maxsize = wi::to_offset (max_object_size ()); - if (pref->sizrng[0] > maxsize) - pref->sizrng[0] = maxsize; - if (pref->sizrng[1] > maxsize) - pref->sizrng[1] = maxsize; - } - else - { - /* For functions known to return one of their pointer arguments - try to determine what the returned pointer points to, and on - success add OFFRNG which was set to the offset added by - the function (e.g., memchr or stpcpy) to the overall offset. - */ - bool past_end; - offset_int offrng[2]; - if (tree ret = gimple_call_return_array (stmt, offrng, - &past_end, rvals)) - { - if (!compute_objsize_r (ret, ostype, pref, snlim, qry)) - return false; - - /* Cap OFFRNG[1] to at most the remaining size of - the object. */ - offset_int remrng[2]; - remrng[1] = pref->size_remaining (remrng); - if (remrng[1] != 0 && !past_end) - /* Decrement the size for functions that never return - a past-the-end pointer. */ - remrng[1] -= 1; - - if (remrng[1] < offrng[1]) - offrng[1] = remrng[1]; - pref->add_offset (offrng[0], offrng[1]); - } - else - { - /* For other calls that might return arbitrary pointers - including into the middle of objects set the size - range to maximum, clear PREF->BASE0, and also set - PREF->REF to include in diagnostics. */ - pref->set_max_size_range (); - pref->base0 = false; - pref->ref = ptr; - } - } - qry->put_ref (ptr, *pref); - return true; - } + /* Check that we can use store by pieces for the maximum store count + we may issue (initial fixed-size block, plus conditional + power-of-two-sized from max_bits to ctz_len. */ + unsigned HOST_WIDE_INT xlenest = blksize; + if (max_bits >= 0) + xlenest += ((HOST_WIDE_INT_1U << max_bits) * 2 + - (HOST_WIDE_INT_1U << ctz_len)); + if (!can_store_by_pieces (xlenest, builtin_memset_read_str, + &valc, align, true)) + return false; - if (gimple_nop_p (stmt)) - { - /* For a function argument try to determine the byte size - of the array from the current function declaratation - (e.g., attribute access or related). */ - wide_int wr[2]; - bool static_array = false; - if (tree ref = gimple_parm_array_size (ptr, wr, &static_array)) - { - pref->parmarray = !static_array; - pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); - pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); - pref->ref = ref; - qry->put_ref (ptr, *pref); - return true; - } + rtx (*constfun) (void *, void *, HOST_WIDE_INT, scalar_int_mode); + void *constfundata; + if (val) + { + constfun = builtin_memset_gen_str; + constfundata = val = force_reg (TYPE_MODE (unsigned_char_type_node), + val); + } + else + { + constfun = builtin_memset_read_str; + constfundata = &valc; + } - pref->set_max_size_range (); - pref->base0 = false; - pref->ref = ptr; - qry->put_ref (ptr, *pref); - return true; - } + rtx ptr = copy_addr_to_reg (convert_to_mode (ptr_mode, XEXP (to, 0), 0)); + rtx rem = copy_to_mode_reg (ptr_mode, convert_to_mode (ptr_mode, len, 0)); + to = replace_equiv_address (to, ptr); + set_mem_align (to, align); - if (gimple_code (stmt) == GIMPLE_PHI) - { - pref->ref = ptr; - access_ref phi_ref = *pref; - if (!pref->get_ref (NULL, &phi_ref, ostype, &snlim, qry)) - return false; - *pref = phi_ref; - pref->ref = ptr; - qry->put_ref (ptr, *pref); - return true; - } + if (blksize) + { + to = store_by_pieces (to, blksize, + constfun, constfundata, + align, true, + max_len != 0 ? RETURN_END : RETURN_BEGIN); + if (max_len == 0) + return true; - if (!is_gimple_assign (stmt)) - { - /* Clear BASE0 since the assigned pointer might point into - the middle of the object, set the maximum size range and, - if the SSA_NAME refers to a function argumnent, set - PREF->REF to it. */ - pref->base0 = false; - pref->set_max_size_range (); - pref->ref = ptr; - return true; - } + /* Adjust PTR, TO and REM. Since TO's address is likely + PTR+offset, we have to replace it. */ + emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX)); + to = replace_equiv_address (to, ptr); + rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize); + emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX)); + } - tree_code code = gimple_assign_rhs_code (stmt); + /* Iterate over power-of-two block sizes from the maximum length to + the least significant bit possibly set in the length. */ + for (int i = max_bits; i >= sctz_len; i--) + { + rtx_code_label *label = NULL; + blksize = HOST_WIDE_INT_1U << i; - if (code == MAX_EXPR || code == MIN_EXPR) + /* If we're past the bits shared between min_ and max_len, expand + a test on the dynamic length, comparing it with the + BLKSIZE. */ + if (i <= tst_bits) { - if (!handle_min_max_size (stmt, ostype, pref, snlim, qry)) - return false; - qry->put_ref (ptr, *pref); - return true; + label = gen_label_rtx (); + emit_cmp_and_jump_insns (rem, GEN_INT (blksize), LT, NULL, + ptr_mode, 1, label, + profile_probability::even ()); } + /* If we are at a bit that is in the prefix shared by min_ and + max_len, skip this BLKSIZE if the bit is clear. */ + else if ((max_len & blksize) == 0) + continue; - tree rhs = gimple_assign_rhs1 (stmt); + /* Issue a store of BLKSIZE bytes. */ + to = store_by_pieces (to, blksize, + constfun, constfundata, + align, true, + i != sctz_len ? RETURN_END : RETURN_BEGIN); - if (code == ASSERT_EXPR) + /* Adjust REM and PTR, unless this is the last iteration. */ + if (i != sctz_len) { - rhs = TREE_OPERAND (rhs, 0); - return compute_objsize_r (rhs, ostype, pref, snlim, qry); + emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX)); + to = replace_equiv_address (to, ptr); + rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize); + emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX)); } - if (code == POINTER_PLUS_EXPR - && TREE_CODE (TREE_TYPE (rhs)) == POINTER_TYPE) + if (label) { - /* Compute the size of the object first. */ - if (!compute_objsize_r (rhs, ostype, pref, snlim, qry)) - return false; - - offset_int orng[2]; - tree off = gimple_assign_rhs2 (stmt); - if (get_offset_range (off, stmt, orng, rvals)) - pref->add_offset (orng[0], orng[1]); - else - pref->add_max_offset (); - qry->put_ref (ptr, *pref); - return true; - } - - if (code == ADDR_EXPR - || code == SSA_NAME) - return compute_objsize_r (rhs, ostype, pref, snlim, qry); + emit_label (label); - /* (This could also be an assignment from a nonlocal pointer.) Save - PTR to mention in diagnostics but otherwise treat it as a pointer - to an unknown object. */ - pref->ref = rhs; - pref->base0 = false; - pref->set_max_size_range (); - return true; + /* Given conditional stores, the offset can no longer be + known, so clear it. */ + clear_mem_offset (to); + } } - /* Assume all other expressions point into an unknown object - of the maximum valid size. */ - pref->ref = ptr; - pref->base0 = false; - pref->set_max_size_range (); - if (TREE_CODE (ptr) == SSA_NAME) - qry->put_ref (ptr, *pref); return true; } -/* A "public" wrapper around the above. Clients should use this overload - instead. */ +/* Helper function to do the actual work for expand_builtin_memset. The + arguments to the builtin_memset call DEST, VAL, and LEN are broken out + so that this can also be called without constructing an actual CALL_EXPR. + The other arguments and return value are the same as for + expand_builtin_memset. */ -tree -compute_objsize (tree ptr, int ostype, access_ref *pref, - range_query *rvals /* = NULL */) +static rtx +expand_builtin_memset_args (tree dest, tree val, tree len, + rtx target, machine_mode mode, tree orig_exp) { - pointer_query qry; - qry.rvals = rvals; - ssa_name_limit_t snlim; - if (!compute_objsize_r (ptr, ostype, pref, snlim, &qry)) - return NULL_TREE; - - offset_int maxsize = pref->size_remaining (); - if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) - pref->offrng[0] = 0; - return wide_int_to_tree (sizetype, maxsize); -} + tree fndecl, fn; + enum built_in_function fcode; + machine_mode val_mode; + char c; + unsigned int dest_align; + rtx dest_mem, dest_addr, len_rtx; + HOST_WIDE_INT expected_size = -1; + unsigned int expected_align = 0; + unsigned HOST_WIDE_INT min_size; + unsigned HOST_WIDE_INT max_size; + unsigned HOST_WIDE_INT probable_max_size; -/* Transitional wrapper. The function should be removed once callers - transition to the pointer_query API. */ + dest_align = get_pointer_alignment (dest); -tree -compute_objsize (tree ptr, int ostype, access_ref *pref, pointer_query *ptr_qry) -{ - pointer_query qry; - if (ptr_qry) - ptr_qry->depth = 0; - else - ptr_qry = &qry; + /* If DEST is not a pointer type, don't do this operation in-line. */ + if (dest_align == 0) + return NULL_RTX; - ssa_name_limit_t snlim; - if (!compute_objsize_r (ptr, ostype, pref, snlim, ptr_qry)) - return NULL_TREE; + if (currently_expanding_gimple_stmt) + stringop_block_profile (currently_expanding_gimple_stmt, + &expected_align, &expected_size); - offset_int maxsize = pref->size_remaining (); - if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) - pref->offrng[0] = 0; - return wide_int_to_tree (sizetype, maxsize); -} + if (expected_align < dest_align) + expected_align = dest_align; -/* Legacy wrapper around the above. The function should be removed - once callers transition to one of the two above. */ + /* If the LEN parameter is zero, return DEST. */ + if (integer_zerop (len)) + { + /* Evaluate and ignore VAL in case it has side-effects. */ + expand_expr (val, const0_rtx, VOIDmode, EXPAND_NORMAL); + return expand_expr (dest, target, mode, EXPAND_NORMAL); + } -tree -compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */, - tree *poff /* = NULL */, range_query *rvals /* = NULL */) -{ - /* Set the initial offsets to zero and size to negative to indicate - none has been computed yet. */ - access_ref ref; - tree size = compute_objsize (ptr, ostype, &ref, rvals); - if (!size || !ref.base0) - return NULL_TREE; + /* Stabilize the arguments in case we fail. */ + dest = builtin_save_expr (dest); + val = builtin_save_expr (val); + len = builtin_save_expr (len); - if (pdecl) - *pdecl = ref.ref; + len_rtx = expand_normal (len); + determine_block_size (len, len_rtx, &min_size, &max_size, + &probable_max_size); + dest_mem = get_memory_rtx (dest, len); + val_mode = TYPE_MODE (unsigned_char_type_node); - if (poff) - *poff = wide_int_to_tree (ptrdiff_type_node, ref.offrng[ref.offrng[0] < 0]); + if (TREE_CODE (val) != INTEGER_CST + || target_char_cast (val, &c)) + { + rtx val_rtx; - return size; -} + val_rtx = expand_normal (val); + val_rtx = convert_to_mode (val_mode, val_rtx, 0); -/* Helper to determine and check the sizes of the source and the destination - of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls. EXP is the - call expression, DEST is the destination argument, SRC is the source - argument or null, and LEN is the number of bytes. Use Object Size type-0 - regardless of the OPT_Wstringop_overflow_ setting. Return true on success - (no overflow or invalid sizes), false otherwise. */ + /* Assume that we can memset by pieces if we can store + * the coefficients by pieces (in the required modes). + * We can't pass builtin_memset_gen_str as that emits RTL. */ + c = 1; + if (tree_fits_uhwi_p (len) + && can_store_by_pieces (tree_to_uhwi (len), + builtin_memset_read_str, &c, dest_align, + true)) + { + val_rtx = force_reg (val_mode, val_rtx); + store_by_pieces (dest_mem, tree_to_uhwi (len), + builtin_memset_gen_str, val_rtx, dest_align, + true, RETURN_BEGIN); + } + else if (!set_storage_via_setmem (dest_mem, len_rtx, val_rtx, + dest_align, expected_align, + expected_size, min_size, max_size, + probable_max_size) + && !try_store_by_multiple_pieces (dest_mem, len_rtx, + tree_ctz (len), + min_size, max_size, + val_rtx, 0, + dest_align)) + goto do_libcall; -static bool -check_memop_access (tree exp, tree dest, tree src, tree size) -{ - /* For functions like memset and memcpy that operate on raw memory - try to determine the size of the largest source and destination - object using type-0 Object Size regardless of the object size - type specified by the option. */ - access_data data (exp, access_read_write); - tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE; - tree dstsize = compute_objsize (dest, 0, &data.dst); + dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX); + dest_mem = convert_memory_address (ptr_mode, dest_mem); + return dest_mem; + } - return check_access (exp, size, /*maxread=*/NULL_TREE, - srcsize, dstsize, data.mode, &data); -} + if (c) + { + if (tree_fits_uhwi_p (len) + && can_store_by_pieces (tree_to_uhwi (len), + builtin_memset_read_str, &c, dest_align, + true)) + store_by_pieces (dest_mem, tree_to_uhwi (len), + builtin_memset_read_str, &c, dest_align, true, + RETURN_BEGIN); + else if (!set_storage_via_setmem (dest_mem, len_rtx, + gen_int_mode (c, val_mode), + dest_align, expected_align, + expected_size, min_size, max_size, + probable_max_size) + && !try_store_by_multiple_pieces (dest_mem, len_rtx, + tree_ctz (len), + min_size, max_size, + NULL_RTX, c, + dest_align)) + goto do_libcall; -/* Validate memchr arguments without performing any expansion. - Return NULL_RTX. */ + dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX); + dest_mem = convert_memory_address (ptr_mode, dest_mem); + return dest_mem; + } -static rtx -expand_builtin_memchr (tree exp, rtx) -{ - if (!validate_arglist (exp, - POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; + set_mem_align (dest_mem, dest_align); + dest_addr = clear_storage_hints (dest_mem, len_rtx, + CALL_EXPR_TAILCALL (orig_exp) + ? BLOCK_OP_TAILCALL : BLOCK_OP_NORMAL, + expected_align, expected_size, + min_size, max_size, + probable_max_size, tree_ctz (len)); - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree len = CALL_EXPR_ARG (exp, 2); + if (dest_addr == 0) + { + dest_addr = force_operand (XEXP (dest_mem, 0), NULL_RTX); + dest_addr = convert_memory_address (ptr_mode, dest_addr); + } - check_read_access (exp, arg1, len, 0); + return dest_addr; - return NULL_RTX; + do_libcall: + fndecl = get_callee_fndecl (orig_exp); + fcode = DECL_FUNCTION_CODE (fndecl); + if (fcode == BUILT_IN_MEMSET) + fn = build_call_nofold_loc (EXPR_LOCATION (orig_exp), fndecl, 3, + dest, val, len); + else if (fcode == BUILT_IN_BZERO) + fn = build_call_nofold_loc (EXPR_LOCATION (orig_exp), fndecl, 2, + dest, len); + else + gcc_unreachable (); + gcc_assert (TREE_CODE (fn) == CALL_EXPR); + CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (orig_exp); + return expand_call (fn, target, target == const0_rtx); } -/* Expand a call EXP to the memcpy builtin. - Return NULL_RTX if we failed, the caller should emit a normal call, - otherwise try to get the result in TARGET, if convenient (and in - mode MODE if that's convenient). */ +/* Expand expression EXP, which is a call to the bzero builtin. Return + NULL_RTX if we failed the caller should emit a normal call. */ static rtx -expand_builtin_memcpy (tree exp, rtx target) +expand_builtin_bzero (tree exp) { - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); + tree size = CALL_EXPR_ARG (exp, 1); - check_memop_access (exp, dest, src, len); + check_memop_access (exp, dest, NULL_TREE, size); - return expand_builtin_memory_copy_args (dest, src, len, target, exp, - /*retmode=*/ RETURN_BEGIN, false); + /* New argument list transforming bzero(ptr x, int y) to + memset(ptr x, int 0, size_t y). This is done this way + so that if it isn't expanded inline, we fallback to + calling bzero instead of memset. */ + + location_t loc = EXPR_LOCATION (exp); + + return expand_builtin_memset_args (dest, integer_zero_node, + fold_convert_loc (loc, + size_type_node, size), + const0_rtx, VOIDmode, exp); } -/* Check a call EXP to the memmove built-in for validity. - Return NULL_RTX on both success and failure. */ +/* Try to expand cmpstr operation ICODE with the given operands. + Return the result rtx on success, otherwise return null. */ static rtx -expand_builtin_memmove (tree exp, rtx target) +expand_cmpstr (insn_code icode, rtx target, rtx arg1_rtx, rtx arg2_rtx, + HOST_WIDE_INT align) { - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; - - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); + machine_mode insn_mode = insn_data[icode].operand[0].mode; - check_memop_access (exp, dest, src, len); + if (target && (!REG_P (target) || HARD_REGISTER_P (target))) + target = NULL_RTX; - return expand_builtin_memory_copy_args (dest, src, len, target, exp, - /*retmode=*/ RETURN_BEGIN, true); + class expand_operand ops[4]; + create_output_operand (&ops[0], target, insn_mode); + create_fixed_operand (&ops[1], arg1_rtx); + create_fixed_operand (&ops[2], arg2_rtx); + create_integer_operand (&ops[3], align); + if (maybe_expand_insn (icode, 4, ops)) + return ops[0].value; + return NULL_RTX; } -/* Expand a call EXP to the mempcpy builtin. - Return NULL_RTX if we failed; the caller should emit a normal call, - otherwise try to get the result in TARGET, if convenient (and in - mode MODE if that's convenient). */ +/* Expand expression EXP, which is a call to the memcmp built-in function. + Return NULL_RTX if we failed and the caller should emit a normal call, + otherwise try to get the result in TARGET, if convenient. + RESULT_EQ is true if we can relax the returned value to be either zero + or nonzero, without caring about the sign. */ static rtx -expand_builtin_mempcpy (tree exp, rtx target) +expand_builtin_memcmp (tree exp, rtx target, bool result_eq) { if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); tree len = CALL_EXPR_ARG (exp, 2); - /* Policy does not generally allow using compute_objsize (which - is used internally by check_memop_size) to change code generation - or drive optimization decisions. - - In this instance it is safe because the code we generate has - the same semantics regardless of the return value of - check_memop_sizes. Exactly the same amount of data is copied - and the return value is exactly the same in both cases. - - Furthermore, check_memop_size always uses mode 0 for the call to - compute_objsize, so the imprecise nature of compute_objsize is - avoided. */ - - /* Avoid expanding mempcpy into memcpy when the call is determined - to overflow the buffer. This also prevents the same overflow - from being diagnosed again when expanding memcpy. */ - if (!check_memop_access (exp, dest, src, len)) + /* Diagnose calls where the specified length exceeds the size of either + object. */ + if (!check_read_access (exp, arg1, len, 0) + || !check_read_access (exp, arg2, len, 0)) return NULL_RTX; - return expand_builtin_mempcpy_args (dest, src, len, - target, exp, /*retmode=*/ RETURN_END); -} - -/* Helper function to do the actual work for expand of memory copy family - functions (memcpy, mempcpy, stpcpy). Expansing should assign LEN bytes - of memory from SRC to DEST and assign to TARGET if convenient. Return - value is based on RETMODE argument. */ + /* Due to the performance benefit, always inline the calls first + when result_eq is false. */ + rtx result = NULL_RTX; + enum built_in_function fcode = DECL_FUNCTION_CODE (get_callee_fndecl (exp)); + if (!result_eq && fcode != BUILT_IN_BCMP) + { + result = inline_expand_builtin_bytecmp (exp, target); + if (result) + return result; + } -static rtx -expand_builtin_memory_copy_args (tree dest, tree src, tree len, - rtx target, tree exp, memop_ret retmode, - bool might_overlap) -{ - unsigned int src_align = get_pointer_alignment (src); - unsigned int dest_align = get_pointer_alignment (dest); - rtx dest_mem, src_mem, dest_addr, len_rtx; - HOST_WIDE_INT expected_size = -1; - unsigned int expected_align = 0; - unsigned HOST_WIDE_INT min_size; - unsigned HOST_WIDE_INT max_size; - unsigned HOST_WIDE_INT probable_max_size; + machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); + location_t loc = EXPR_LOCATION (exp); - bool is_move_done; + unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; + unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; - /* If DEST is not a pointer type, call the normal function. */ - if (dest_align == 0) + /* If we don't have POINTER_TYPE, call the function. */ + if (arg1_align == 0 || arg2_align == 0) return NULL_RTX; - /* If either SRC is not a pointer type, don't do this - operation in-line. */ - if (src_align == 0) - return NULL_RTX; + rtx arg1_rtx = get_memory_rtx (arg1, len); + rtx arg2_rtx = get_memory_rtx (arg2, len); + rtx len_rtx = expand_normal (fold_convert_loc (loc, sizetype, len)); - if (currently_expanding_gimple_stmt) - stringop_block_profile (currently_expanding_gimple_stmt, - &expected_align, &expected_size); + /* Set MEM_SIZE as appropriate. */ + if (CONST_INT_P (len_rtx)) + { + set_mem_size (arg1_rtx, INTVAL (len_rtx)); + set_mem_size (arg2_rtx, INTVAL (len_rtx)); + } - if (expected_align < dest_align) - expected_align = dest_align; - dest_mem = get_memory_rtx (dest, len); - set_mem_align (dest_mem, dest_align); - len_rtx = expand_normal (len); - determine_block_size (len, len_rtx, &min_size, &max_size, - &probable_max_size); + by_pieces_constfn constfn = NULL; - /* Try to get the byte representation of the constant SRC points to, - with its byte size in NBYTES. */ + /* Try to get the byte representation of the constant ARG2 (or, only + when the function's result is used for equality to zero, ARG1) + points to, with its byte size in NBYTES. */ unsigned HOST_WIDE_INT nbytes; - const char *rep = getbyterep (src, &nbytes); + const char *rep = getbyterep (arg2, &nbytes); + if (result_eq && rep == NULL) + { + /* For equality to zero the arguments are interchangeable. */ + rep = getbyterep (arg1, &nbytes); + if (rep != NULL) + std::swap (arg1_rtx, arg2_rtx); + } /* If the function's constant bound LEN_RTX is less than or equal to the byte size of the representation of the constant argument, and if block move would be done by pieces, we can avoid loading - the bytes from memory and only store the computed constant. - This works in the overlap (memmove) case as well because - store_by_pieces just generates a series of stores of constants - from the representation returned by getbyterep(). */ + the bytes from memory and only store the computed constant result. */ if (rep && CONST_INT_P (len_rtx) - && (unsigned HOST_WIDE_INT) INTVAL (len_rtx) <= nbytes - && can_store_by_pieces (INTVAL (len_rtx), builtin_memcpy_read_str, - CONST_CAST (char *, rep), - dest_align, false)) - { - dest_mem = store_by_pieces (dest_mem, INTVAL (len_rtx), - builtin_memcpy_read_str, - CONST_CAST (char *, rep), - dest_align, false, retmode); - dest_mem = force_operand (XEXP (dest_mem, 0), target); - dest_mem = convert_memory_address (ptr_mode, dest_mem); - return dest_mem; - } + && (unsigned HOST_WIDE_INT) INTVAL (len_rtx) <= nbytes) + constfn = builtin_memcpy_read_str; - src_mem = get_memory_rtx (src, len); - set_mem_align (src_mem, src_align); + result = emit_block_cmp_hints (arg1_rtx, arg2_rtx, len_rtx, + TREE_TYPE (len), target, + result_eq, constfn, + CONST_CAST (char *, rep)); - /* Copy word part most expediently. */ - enum block_op_methods method = BLOCK_OP_NORMAL; - if (CALL_EXPR_TAILCALL (exp) - && (retmode == RETURN_BEGIN || target == const0_rtx)) - method = BLOCK_OP_TAILCALL; - bool use_mempcpy_call = (targetm.libc_has_fast_function (BUILT_IN_MEMPCPY) - && retmode == RETURN_END - && !might_overlap - && target != const0_rtx); - if (use_mempcpy_call) - method = BLOCK_OP_NO_LIBCALL_RET; - dest_addr = emit_block_move_hints (dest_mem, src_mem, len_rtx, method, - expected_align, expected_size, - min_size, max_size, probable_max_size, - use_mempcpy_call, &is_move_done, - might_overlap); + if (result) + { + /* Return the value in the proper mode for this function. */ + if (GET_MODE (result) == mode) + return result; - /* Bail out when a mempcpy call would be expanded as libcall and when - we have a target that provides a fast implementation - of mempcpy routine. */ - if (!is_move_done) - return NULL_RTX; + if (target != 0) + { + convert_move (target, result, 0); + return target; + } - if (dest_addr == pc_rtx) - return NULL_RTX; + return convert_to_mode (mode, result, 0); + } - if (dest_addr == 0) - { - dest_addr = force_operand (XEXP (dest_mem, 0), target); - dest_addr = convert_memory_address (ptr_mode, dest_addr); - } - - if (retmode != RETURN_BEGIN && target != const0_rtx) - { - dest_addr = gen_rtx_PLUS (ptr_mode, dest_addr, len_rtx); - /* stpcpy pointer to last byte. */ - if (retmode == RETURN_END_MINUS_ONE) - dest_addr = gen_rtx_MINUS (ptr_mode, dest_addr, const1_rtx); - } - - return dest_addr; -} - -static rtx -expand_builtin_mempcpy_args (tree dest, tree src, tree len, - rtx target, tree orig_exp, memop_ret retmode) -{ - return expand_builtin_memory_copy_args (dest, src, len, target, orig_exp, - retmode, false); + return NULL_RTX; } -/* Expand into a movstr instruction, if one is available. Return NULL_RTX if - we failed, the caller should emit a normal call, otherwise try to - get the result in TARGET, if convenient. - Return value is based on RETMODE argument. */ +/* Expand expression EXP, which is a call to the strcmp builtin. Return NULL_RTX + if we failed the caller should emit a normal call, otherwise try to get + the result in TARGET, if convenient. */ static rtx -expand_movstr (tree dest, tree src, rtx target, memop_ret retmode) +expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target) { - class expand_operand ops[3]; - rtx dest_mem; - rtx src_mem; - - if (!targetm.have_movstr ()) + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) return NULL_RTX; - dest_mem = get_memory_rtx (dest, NULL); - src_mem = get_memory_rtx (src, NULL); - if (retmode == RETURN_BEGIN) - { - target = force_reg (Pmode, XEXP (dest_mem, 0)); - dest_mem = replace_equiv_address (dest_mem, target); - } + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); - create_output_operand (&ops[0], - retmode != RETURN_BEGIN ? target : NULL_RTX, Pmode); - create_fixed_operand (&ops[1], dest_mem); - create_fixed_operand (&ops[2], src_mem); - if (!maybe_expand_insn (targetm.code_for_movstr, 3, ops)) + if (!check_read_access (exp, arg1) + || !check_read_access (exp, arg2)) return NULL_RTX; - if (retmode != RETURN_BEGIN && target != const0_rtx) - { - target = ops[0].value; - /* movstr is supposed to set end to the address of the NUL - terminator. If the caller requested a mempcpy-like return value, - adjust it. */ - if (retmode == RETURN_END) - { - rtx tem = plus_constant (GET_MODE (target), - gen_lowpart (GET_MODE (target), target), 1); - emit_move_insn (target, force_operand (tem, NULL_RTX)); - } - } - return target; -} + /* Due to the performance benefit, always inline the calls first. */ + rtx result = NULL_RTX; + result = inline_expand_builtin_bytecmp (exp, target); + if (result) + return result; -/* Do some very basic size validation of a call to the strcpy builtin - given by EXP. Return NULL_RTX to have the built-in expand to a call - to the library function. */ + insn_code cmpstr_icode = direct_optab_handler (cmpstr_optab, SImode); + insn_code cmpstrn_icode = direct_optab_handler (cmpstrn_optab, SImode); + if (cmpstr_icode == CODE_FOR_nothing && cmpstrn_icode == CODE_FOR_nothing) + return NULL_RTX; -static rtx -expand_builtin_strcat (tree exp) -{ - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE) - || !warn_stringop_overflow) + unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; + unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; + + /* If we don't have POINTER_TYPE, call the function. */ + if (arg1_align == 0 || arg2_align == 0) return NULL_RTX; - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); + /* Stabilize the arguments in case gen_cmpstr(n)si fail. */ + arg1 = builtin_save_expr (arg1); + arg2 = builtin_save_expr (arg2); - /* There is no way here to determine the length of the string in - the destination to which the SRC string is being appended so - just diagnose cases when the souce string is longer than - the destination object. */ - access_data data (exp, access_read_write, NULL_TREE, true, - NULL_TREE, true); - const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree destsize = compute_objsize (dest, ost, &data.dst); + rtx arg1_rtx = get_memory_rtx (arg1, NULL); + rtx arg2_rtx = get_memory_rtx (arg2, NULL); - check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE, - src, destsize, data.mode, &data); + /* Try to call cmpstrsi. */ + if (cmpstr_icode != CODE_FOR_nothing) + result = expand_cmpstr (cmpstr_icode, target, arg1_rtx, arg2_rtx, + MIN (arg1_align, arg2_align)); - return NULL_RTX; -} + /* Try to determine at least one length and call cmpstrnsi. */ + if (!result && cmpstrn_icode != CODE_FOR_nothing) + { + tree len; + rtx arg3_rtx; -/* Expand expression EXP, which is a call to the strcpy builtin. Return - NULL_RTX if we failed the caller should emit a normal call, otherwise - try to get the result in TARGET, if convenient (and in mode MODE if that's - convenient). */ + tree len1 = c_strlen (arg1, 1); + tree len2 = c_strlen (arg2, 1); -static rtx -expand_builtin_strcpy (tree exp, rtx target) -{ - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - return NULL_RTX; + if (len1) + len1 = size_binop (PLUS_EXPR, ssize_int (1), len1); + if (len2) + len2 = size_binop (PLUS_EXPR, ssize_int (1), len2); - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); + /* If we don't have a constant length for the first, use the length + of the second, if we know it. We don't require a constant for + this case; some cost analysis could be done if both are available + but neither is constant. For now, assume they're equally cheap, + unless one has side effects. If both strings have constant lengths, + use the smaller. */ - if (warn_stringop_overflow) - { - access_data data (exp, access_read_write, NULL_TREE, true, - NULL_TREE, true); - const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree dstsize = compute_objsize (dest, ost, &data.dst); - check_access (exp, /*dstwrite=*/ NULL_TREE, - /*maxread=*/ NULL_TREE, /*srcstr=*/ src, - dstsize, data.mode, &data); + if (!len1) + len = len2; + else if (!len2) + len = len1; + else if (TREE_SIDE_EFFECTS (len1)) + len = len2; + else if (TREE_SIDE_EFFECTS (len2)) + len = len1; + else if (TREE_CODE (len1) != INTEGER_CST) + len = len2; + else if (TREE_CODE (len2) != INTEGER_CST) + len = len1; + else if (tree_int_cst_lt (len1, len2)) + len = len1; + else + len = len2; + + /* If both arguments have side effects, we cannot optimize. */ + if (len && !TREE_SIDE_EFFECTS (len)) + { + arg3_rtx = expand_normal (len); + result = expand_cmpstrn_or_cmpmem + (cmpstrn_icode, target, arg1_rtx, arg2_rtx, TREE_TYPE (len), + arg3_rtx, MIN (arg1_align, arg2_align)); + } } - if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target)) + tree fndecl = get_callee_fndecl (exp); + if (result) { /* Check to see if the argument was declared attribute nonstring and if so, issue a warning since at this point it's not known to be nul-terminated. */ - tree fndecl = get_callee_fndecl (exp); maybe_warn_nonstring_arg (fndecl, exp); - return ret; - } - - return NULL_RTX; -} - -/* Helper function to do the actual work for expand_builtin_strcpy. The - arguments to the builtin_strcpy call DEST and SRC are broken out - so that this can also be called without constructing an actual CALL_EXPR. - The other arguments and return value are the same as for - expand_builtin_strcpy. */ -static rtx -expand_builtin_strcpy_args (tree exp, tree dest, tree src, rtx target) -{ - /* Detect strcpy calls with unterminated arrays.. */ - tree size; - bool exact; - if (tree nonstr = unterminated_array (src, &size, &exact)) - { - /* NONSTR refers to the non-nul terminated constant array. */ - warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, nonstr, - size, exact); - return NULL_RTX; + /* Return the value in the proper mode for this function. */ + machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); + if (GET_MODE (result) == mode) + return result; + if (target == 0) + return convert_to_mode (mode, result, 0); + convert_move (target, result, 0); + return target; } - return expand_movstr (dest, src, target, /*retmode=*/ RETURN_BEGIN); + /* Expand the library call ourselves using a stabilized argument + list to avoid re-evaluating the function's arguments twice. */ + tree fn = build_call_nofold_loc (EXPR_LOCATION (exp), fndecl, 2, arg1, arg2); + gcc_assert (TREE_CODE (fn) == CALL_EXPR); + CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); + return expand_call (fn, target, target == const0_rtx); } -/* Expand a call EXP to the stpcpy builtin. - Return NULL_RTX if we failed the caller should emit a normal call, - otherwise try to get the result in TARGET, if convenient (and in - mode MODE if that's convenient). */ +/* Expand expression EXP, which is a call to the strncmp builtin. Return + NULL_RTX if we failed the caller should emit a normal call, otherwise + try to get the result in TARGET, if convenient. */ static rtx -expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode) +expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, + ATTRIBUTE_UNUSED machine_mode mode) { - tree dst, src; - location_t loc = EXPR_LOCATION (exp); + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); + tree arg3 = CALL_EXPR_ARG (exp, 2); + + if (!check_nul_terminated_array (exp, arg1, arg3) + || !check_nul_terminated_array (exp, arg2, arg3)) return NULL_RTX; - dst = CALL_EXPR_ARG (exp, 0); - src = CALL_EXPR_ARG (exp, 1); + location_t loc = EXPR_LOCATION (exp); + tree len1 = c_strlen (arg1, 1); + tree len2 = c_strlen (arg2, 1); - if (warn_stringop_overflow) + if (!len1 || !len2) { - access_data data (exp, access_read_write); - tree destsize = compute_objsize (dst, warn_stringop_overflow - 1, - &data.dst); - check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE, - src, destsize, data.mode, &data); - } - - /* If return value is ignored, transform stpcpy into strcpy. */ - if (target == const0_rtx && builtin_decl_implicit (BUILT_IN_STRCPY)) - { - tree fn = builtin_decl_implicit (BUILT_IN_STRCPY); - tree result = build_call_nofold_loc (loc, fn, 2, dst, src); - return expand_expr (result, target, mode, EXPAND_NORMAL); - } - else - { - tree len, lenp1; - rtx ret; - - /* Ensure we get an actual string whose length can be evaluated at - compile-time, not an expression containing a string. This is - because the latter will potentially produce pessimized code - when used to produce the return value. */ - c_strlen_data lendata = { }; - if (!c_getstr (src) - || !(len = c_strlen (src, 0, &lendata, 1))) - return expand_movstr (dst, src, target, - /*retmode=*/ RETURN_END_MINUS_ONE); - - if (lendata.decl) - warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, lendata.decl); + /* Check to see if the argument was declared attribute nonstring + and if so, issue a warning since at this point it's not known + to be nul-terminated. */ + if (!maybe_warn_nonstring_arg (get_callee_fndecl (exp), exp) + && !len1 && !len2) + { + /* A strncmp read is constrained not just by the bound but + also by the length of the shorter string. Specifying + a bound that's larger than the size of either array makes + no sense and is likely a bug. When the length of neither + of the two strings is known but the sizes of both of + the arrays they are stored in is, issue a warning if + the bound is larger than than the size of the larger + of the two arrays. */ - lenp1 = size_binop_loc (loc, PLUS_EXPR, len, ssize_int (1)); - ret = expand_builtin_mempcpy_args (dst, src, lenp1, - target, exp, - /*retmode=*/ RETURN_END_MINUS_ONE); + access_ref ref1 (arg3, true); + access_ref ref2 (arg3, true); - if (ret) - return ret; + tree bndrng[2] = { NULL_TREE, NULL_TREE }; + get_size_range (arg3, bndrng, ref1.bndrng); - if (TREE_CODE (len) == INTEGER_CST) - { - rtx len_rtx = expand_normal (len); + tree size1 = compute_objsize (arg1, 1, &ref1); + tree size2 = compute_objsize (arg2, 1, &ref2); + tree func = get_callee_fndecl (exp); - if (CONST_INT_P (len_rtx)) + if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0])) { - ret = expand_builtin_strcpy_args (exp, dst, src, target); - - if (ret) + offset_int rem1 = ref1.size_remaining (); + offset_int rem2 = ref2.size_remaining (); + if (rem1 == 0 || rem2 == 0) + maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, + bndrng, integer_zero_node); + else { - if (! target) - { - if (mode != VOIDmode) - target = gen_reg_rtx (mode); - else - target = gen_reg_rtx (GET_MODE (ret)); - } - if (GET_MODE (target) != GET_MODE (ret)) - ret = gen_lowpart (GET_MODE (target), ret); - - ret = plus_constant (GET_MODE (ret), ret, INTVAL (len_rtx)); - ret = emit_move_insn (target, force_operand (ret, NULL_RTX)); - gcc_assert (ret); - - return target; + offset_int maxrem = wi::max (rem1, rem2, UNSIGNED); + if (maxrem < wi::to_offset (bndrng[0])) + maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, + func, bndrng, + wide_int_to_tree (sizetype, maxrem)); } } + else if (bndrng[0] + && !integer_zerop (bndrng[0]) + && ((size1 && integer_zerop (size1)) + || (size2 && integer_zerop (size2)))) + maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, + bndrng, integer_zero_node); } - - return expand_movstr (dst, src, target, - /*retmode=*/ RETURN_END_MINUS_ONE); } -} -/* Expand a call EXP to the stpcpy builtin and diagnose uses of nonstring - arguments while being careful to avoid duplicate warnings (which could - be issued if the expander were to expand the call, resulting in it - being emitted in expand_call(). */ + /* Due to the performance benefit, always inline the calls first. */ + rtx result = NULL_RTX; + result = inline_expand_builtin_bytecmp (exp, target); + if (result) + return result; -static rtx -expand_builtin_stpcpy (tree exp, rtx target, machine_mode mode) -{ - if (rtx ret = expand_builtin_stpcpy_1 (exp, target, mode)) - { - /* The call has been successfully expanded. Check for nonstring - arguments and issue warnings as appropriate. */ - maybe_warn_nonstring_arg (get_callee_fndecl (exp), exp); - return ret; - } + /* If c_strlen can determine an expression for one of the string + lengths, and it doesn't have side effects, then emit cmpstrnsi + using length MIN(strlen(string)+1, arg3). */ + insn_code cmpstrn_icode = direct_optab_handler (cmpstrn_optab, SImode); + if (cmpstrn_icode == CODE_FOR_nothing) + return NULL_RTX; - return NULL_RTX; -} + tree len; -/* Check a call EXP to the stpncpy built-in for validity. - Return NULL_RTX on both success and failure. */ + unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; + unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; -static rtx -expand_builtin_stpncpy (tree exp, rtx) -{ - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) - || !warn_stringop_overflow) - return NULL_RTX; + if (len1) + len1 = size_binop_loc (loc, PLUS_EXPR, ssize_int (1), len1); + if (len2) + len2 = size_binop_loc (loc, PLUS_EXPR, ssize_int (1), len2); - /* The source and destination of the call. */ - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); + tree len3 = fold_convert_loc (loc, sizetype, arg3); - /* The exact number of bytes to write (not the maximum). */ - tree len = CALL_EXPR_ARG (exp, 2); - access_data data (exp, access_read_write); - /* The size of the destination object. */ - tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); - check_access (exp, len, /*maxread=*/len, src, destsize, data.mode, &data); - return NULL_RTX; -} + /* If we don't have a constant length for the first, use the length + of the second, if we know it. If neither string is constant length, + use the given length argument. We don't require a constant for + this case; some cost analysis could be done if both are available + but neither is constant. For now, assume they're equally cheap, + unless one has side effects. If both strings have constant lengths, + use the smaller. */ -/* Callback routine for store_by_pieces. Read GET_MODE_BITSIZE (MODE) - bytes from constant string DATA + OFFSET and return it as target - constant. */ + if (!len1 && !len2) + len = len3; + else if (!len1) + len = len2; + else if (!len2) + len = len1; + else if (TREE_SIDE_EFFECTS (len1)) + len = len2; + else if (TREE_SIDE_EFFECTS (len2)) + len = len1; + else if (TREE_CODE (len1) != INTEGER_CST) + len = len2; + else if (TREE_CODE (len2) != INTEGER_CST) + len = len1; + else if (tree_int_cst_lt (len1, len2)) + len = len1; + else + len = len2; -rtx -builtin_strncpy_read_str (void *data, void *, HOST_WIDE_INT offset, - scalar_int_mode mode) -{ - const char *str = (const char *) data; + /* If we are not using the given length, we must incorporate it here. + The actual new length parameter will be MIN(len,arg3) in this case. */ + if (len != len3) + { + len = fold_convert_loc (loc, sizetype, len); + len = fold_build2_loc (loc, MIN_EXPR, TREE_TYPE (len), len, len3); + } + rtx arg1_rtx = get_memory_rtx (arg1, len); + rtx arg2_rtx = get_memory_rtx (arg2, len); + rtx arg3_rtx = expand_normal (len); + result = expand_cmpstrn_or_cmpmem (cmpstrn_icode, target, arg1_rtx, + arg2_rtx, TREE_TYPE (len), arg3_rtx, + MIN (arg1_align, arg2_align)); - if ((unsigned HOST_WIDE_INT) offset > strlen (str)) - return const0_rtx; + tree fndecl = get_callee_fndecl (exp); + if (result) + { + /* Return the value in the proper mode for this function. */ + mode = TYPE_MODE (TREE_TYPE (exp)); + if (GET_MODE (result) == mode) + return result; + if (target == 0) + return convert_to_mode (mode, result, 0); + convert_move (target, result, 0); + return target; + } - return c_readstr (str + offset, mode); + /* Expand the library call ourselves using a stabilized argument + list to avoid re-evaluating the function's arguments twice. */ + tree call = build_call_nofold_loc (loc, fndecl, 3, arg1, arg2, len); + copy_warning (call, exp); + gcc_assert (TREE_CODE (call) == CALL_EXPR); + CALL_EXPR_TAILCALL (call) = CALL_EXPR_TAILCALL (exp); + return expand_call (call, target, target == const0_rtx); } -/* Helper to check the sizes of sequences and the destination of calls - to __builtin_strncat and __builtin___strncat_chk. Returns true on - success (no overflow or invalid sizes), false otherwise. */ +/* Expand a call to __builtin_saveregs, generating the result in TARGET, + if that's convenient. */ -static bool -check_strncat_sizes (tree exp, tree objsize) +rtx +expand_builtin_saveregs (void) { - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree maxread = CALL_EXPR_ARG (exp, 2); + rtx val; + rtx_insn *seq; - /* Try to determine the range of lengths that the source expression - refers to. */ - c_strlen_data lendata = { }; - get_range_strlen (src, &lendata, /* eltsize = */ 1); + /* Don't do __builtin_saveregs more than once in a function. + Save the result of the first call and reuse it. */ + if (saveregs_value != 0) + return saveregs_value; - /* Try to verify that the destination is big enough for the shortest - string. */ + /* 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. */ - access_data data (exp, access_read_write, maxread, true); - if (!objsize && warn_stringop_overflow) - { - /* If it hasn't been provided by __strncat_chk, try to determine - the size of the destination object into which the source is - being copied. */ - objsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); - } + start_sequence (); - /* Add one for the terminating nul. */ - tree srclen = (lendata.minlen - ? fold_build2 (PLUS_EXPR, size_type_node, lendata.minlen, - size_one_node) - : NULL_TREE); + /* Do whatever the machine needs done in this case. */ + val = targetm.calls.expand_builtin_saveregs (); - /* The strncat function copies at most MAXREAD bytes and always appends - the terminating nul so the specified upper bound should never be equal - to (or greater than) the size of the destination. */ - if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize) - && tree_int_cst_equal (objsize, maxread)) - { - location_t loc = EXPR_LOCATION (exp); - warning_at (loc, OPT_Wstringop_overflow_, - "%qD specified bound %E equals destination size", - get_callee_fndecl (exp), maxread); + seq = get_insns (); + end_sequence (); - return false; - } + saveregs_value = val; - if (!srclen - || (maxread && tree_fits_uhwi_p (maxread) - && tree_fits_uhwi_p (srclen) - && tree_int_cst_lt (maxread, srclen))) - srclen = maxread; + /* 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. */ + push_topmost_sequence (); + emit_insn_after (seq, entry_of_function ()); + pop_topmost_sequence (); - /* The number of bytes to write is LEN but check_access will alsoa - check SRCLEN if LEN's value isn't known. */ - return check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen, - objsize, data.mode, &data); + return val; } -/* Similar to expand_builtin_strcat, do some very basic size validation - of a call to the strcpy builtin given by EXP. Return NULL_RTX to have - the built-in expand to a call to the library function. */ +/* Expand a call to __builtin_next_arg. */ static rtx -expand_builtin_strncat (tree exp, rtx) +expand_builtin_next_arg (void) { - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) - || !warn_stringop_overflow) - return NULL_RTX; + /* Checking arguments is already done in fold_builtin_next_arg + that must be called before this function. */ + return expand_binop (ptr_mode, add_optab, + crtl->args.internal_arg_pointer, + crtl->args.arg_offset_rtx, + NULL_RTX, 0, OPTAB_LIB_WIDEN); +} - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - /* The upper bound on the number of bytes to write. */ - tree maxread = CALL_EXPR_ARG (exp, 2); +/* Make it easier for the backends by protecting the valist argument + from multiple evaluations. */ - /* Detect unterminated source (only). */ - if (!check_nul_terminated_array (exp, src, maxread)) - return NULL_RTX; +static tree +stabilize_va_list_loc (location_t loc, tree valist, int needs_lvalue) +{ + tree vatype = targetm.canonical_va_list_type (TREE_TYPE (valist)); - /* The length of the source sequence. */ - tree slen = c_strlen (src, 1); + /* The current way of determining the type of valist is completely + bogus. We should have the information on the va builtin instead. */ + if (!vatype) + vatype = targetm.fn_abi_va_list (cfun->decl); - /* Try to determine the range of lengths that the source expression - refers to. Since the lengths are only used for warning and not - for code generation disable strict mode below. */ - tree maxlen = slen; - if (!maxlen) + if (TREE_CODE (vatype) == ARRAY_TYPE) { - c_strlen_data lendata = { }; - get_range_strlen (src, &lendata, /* eltsize = */ 1); - maxlen = lendata.maxbound; - } + if (TREE_SIDE_EFFECTS (valist)) + valist = save_expr (valist); - access_data data (exp, access_read_write); - /* Try to verify that the destination is big enough for the shortest - string. First try to determine the size of the destination object - into which the source is being copied. */ - tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst); + /* For this case, the backends will be expecting a pointer to + vatype, but it's possible we've actually been given an array + (an actual TARGET_CANONICAL_VA_LIST_TYPE (valist)). + So fix it. */ + if (TREE_CODE (TREE_TYPE (valist)) == ARRAY_TYPE) + { + tree p1 = build_pointer_type (TREE_TYPE (vatype)); + valist = build_fold_addr_expr_with_type_loc (loc, valist, p1); + } + } + else + { + tree pt = build_pointer_type (vatype); - /* Add one for the terminating nul. */ - tree srclen = (maxlen - ? fold_build2 (PLUS_EXPR, size_type_node, maxlen, - size_one_node) - : NULL_TREE); + if (! needs_lvalue) + { + if (! TREE_SIDE_EFFECTS (valist)) + return valist; - /* The strncat function copies at most MAXREAD bytes and always appends - the terminating nul so the specified upper bound should never be equal - to (or greater than) the size of the destination. */ - if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize) - && tree_int_cst_equal (destsize, maxread)) - { - location_t loc = EXPR_LOCATION (exp); - warning_at (loc, OPT_Wstringop_overflow_, - "%qD specified bound %E equals destination size", - get_callee_fndecl (exp), maxread); + valist = fold_build1_loc (loc, ADDR_EXPR, pt, valist); + TREE_SIDE_EFFECTS (valist) = 1; + } - return NULL_RTX; + if (TREE_SIDE_EFFECTS (valist)) + valist = save_expr (valist); + valist = fold_build2_loc (loc, MEM_REF, + vatype, valist, build_int_cst (pt, 0)); } - if (!srclen - || (maxread && tree_fits_uhwi_p (maxread) - && tree_fits_uhwi_p (srclen) - && tree_int_cst_lt (maxread, srclen))) - srclen = maxread; - - check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen, - destsize, data.mode, &data); - return NULL_RTX; + return valist; } -/* Expand expression EXP, which is a call to the strncpy builtin. Return - NULL_RTX if we failed the caller should emit a normal call. */ +/* The "standard" definition of va_list is void*. */ -static rtx -expand_builtin_strncpy (tree exp, rtx target) +tree +std_build_builtin_va_list (void) { - location_t loc = EXPR_LOCATION (exp); + return ptr_type_node; +} - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - /* The number of bytes to write (not the maximum). */ - tree len = CALL_EXPR_ARG (exp, 2); +/* The "standard" abi va_list is va_list_type_node. */ - /* The length of the source sequence. */ - tree slen = c_strlen (src, 1); +tree +std_fn_abi_va_list (tree fndecl ATTRIBUTE_UNUSED) +{ + return va_list_type_node; +} - if (warn_stringop_overflow) - { - access_data data (exp, access_read_write, len, true, len, true); - const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1; - compute_objsize (src, ost, &data.src); - tree dstsize = compute_objsize (dest, ost, &data.dst); - /* The number of bytes to write is LEN but check_access will also - check SLEN if LEN's value isn't known. */ - check_access (exp, /*dstwrite=*/len, - /*maxread=*/len, src, dstsize, data.mode, &data); - } +/* The "standard" type of va_list is va_list_type_node. */ - /* We must be passed a constant len and src parameter. */ - if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen)) - return NULL_RTX; +tree +std_canonical_va_list_type (tree type) +{ + tree wtype, htype; - slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1)); + wtype = va_list_type_node; + htype = type; - /* We're required to pad with trailing zeros if the requested - len is greater than strlen(s2)+1. In that case try to - use store_by_pieces, if it fails, punt. */ - if (tree_int_cst_lt (slen, len)) + if (TREE_CODE (wtype) == ARRAY_TYPE) { - unsigned int dest_align = get_pointer_alignment (dest); - const char *p = c_getstr (src); - rtx dest_mem; + /* If va_list is an array type, the argument may have decayed + to a pointer type, e.g. by being passed to another function. + In that case, unwrap both types so that we can compare the + underlying records. */ + if (TREE_CODE (htype) == ARRAY_TYPE + || POINTER_TYPE_P (htype)) + { + wtype = TREE_TYPE (wtype); + htype = TREE_TYPE (htype); + } + } + if (TYPE_MAIN_VARIANT (wtype) == TYPE_MAIN_VARIANT (htype)) + return va_list_type_node; - if (!p || dest_align == 0 || !tree_fits_uhwi_p (len) - || !can_store_by_pieces (tree_to_uhwi (len), - builtin_strncpy_read_str, - CONST_CAST (char *, p), - dest_align, false)) - return NULL_RTX; + return NULL_TREE; +} - dest_mem = get_memory_rtx (dest, len); - store_by_pieces (dest_mem, tree_to_uhwi (len), - builtin_strncpy_read_str, - CONST_CAST (char *, p), dest_align, false, - RETURN_BEGIN); - dest_mem = force_operand (XEXP (dest_mem, 0), target); - dest_mem = convert_memory_address (ptr_mode, dest_mem); - return dest_mem; - } +/* The "standard" implementation of va_start: just assign `nextarg' to + the variable. */ - return NULL_RTX; +void +std_expand_builtin_va_start (tree valist, rtx nextarg) +{ + rtx va_r = expand_expr (valist, NULL_RTX, VOIDmode, EXPAND_WRITE); + convert_move (va_r, nextarg, 0); } -/* Callback routine for store_by_pieces. Read GET_MODE_BITSIZE (MODE) - bytes from constant string DATA + OFFSET and return it as target - constant. If PREV isn't nullptr, it has the RTL info from the - previous iteration. */ +/* Expand EXP, a call to __builtin_va_start. */ -rtx -builtin_memset_read_str (void *data, void *prevp, - HOST_WIDE_INT offset ATTRIBUTE_UNUSED, - scalar_int_mode mode) +static rtx +expand_builtin_va_start (tree exp) { - by_pieces_prev *prev = (by_pieces_prev *) prevp; - if (prev != nullptr && prev->data != nullptr) + rtx nextarg; + tree valist; + location_t loc = EXPR_LOCATION (exp); + + if (call_expr_nargs (exp) < 2) { - /* Use the previous data in the same mode. */ - if (prev->mode == mode) - return prev->data; + error_at (loc, "too few arguments to function %"); + return const0_rtx; } - const char *c = (const char *) data; - char *p = XALLOCAVEC (char, GET_MODE_SIZE (mode)); + if (fold_builtin_next_arg (exp, true)) + return const0_rtx; - memset (p, *c, GET_MODE_SIZE (mode)); + nextarg = expand_builtin_next_arg (); + valist = stabilize_va_list_loc (loc, CALL_EXPR_ARG (exp, 0), 1); - return c_readstr (p, mode); + if (targetm.expand_builtin_va_start) + targetm.expand_builtin_va_start (valist, nextarg); + else + std_expand_builtin_va_start (valist, nextarg); + + return const0_rtx; } -/* Callback routine for store_by_pieces. Return the RTL of a register - containing GET_MODE_SIZE (MODE) consecutive copies of the unsigned - char value given in the RTL register data. For example, if mode is - 4 bytes wide, return the RTL for 0x01010101*data. If PREV isn't - nullptr, it has the RTL info from the previous iteration. */ +/* Expand EXP, a call to __builtin_va_end. */ static rtx -builtin_memset_gen_str (void *data, void *prevp, - HOST_WIDE_INT offset ATTRIBUTE_UNUSED, - scalar_int_mode mode) +expand_builtin_va_end (tree exp) { - rtx target, coeff; - size_t size; - char *p; - - by_pieces_prev *prev = (by_pieces_prev *) prevp; - if (prev != nullptr && prev->data != nullptr) - { - /* Use the previous data in the same mode. */ - if (prev->mode == mode) - return prev->data; - - target = simplify_gen_subreg (mode, prev->data, prev->mode, 0); - if (target != nullptr) - return target; - } - - size = GET_MODE_SIZE (mode); - if (size == 1) - return (rtx) data; + tree valist = CALL_EXPR_ARG (exp, 0); - p = XALLOCAVEC (char, size); - memset (p, 1, size); - coeff = c_readstr (p, mode); + /* Evaluate for side effects, if needed. I hate macros that don't + do that. */ + if (TREE_SIDE_EFFECTS (valist)) + expand_expr (valist, const0_rtx, VOIDmode, EXPAND_NORMAL); - target = convert_to_mode (mode, (rtx) data, 1); - target = expand_mult (mode, target, coeff, NULL_RTX, 1); - return force_reg (mode, target); + return const0_rtx; } -/* Expand expression EXP, which is a call to the memset builtin. Return - NULL_RTX if we failed the caller should emit a normal call, otherwise - try to get the result in TARGET, if convenient (and in mode MODE if that's - convenient). */ +/* Expand EXP, a call to __builtin_va_copy. We do this as a + builtin rather than just as an assignment in stdarg.h because of the + nastiness of array-type va_list types. */ static rtx -expand_builtin_memset (tree exp, rtx target, machine_mode mode) +expand_builtin_va_copy (tree exp) { - if (!validate_arglist (exp, - POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; + tree dst, src, t; + location_t loc = EXPR_LOCATION (exp); - tree dest = CALL_EXPR_ARG (exp, 0); - tree val = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); + dst = CALL_EXPR_ARG (exp, 0); + src = CALL_EXPR_ARG (exp, 1); - check_memop_access (exp, dest, NULL_TREE, len); + dst = stabilize_va_list_loc (loc, dst, 1); + src = stabilize_va_list_loc (loc, src, 0); - return expand_builtin_memset_args (dest, val, len, target, mode, exp); -} + gcc_assert (cfun != NULL && cfun->decl != NULL_TREE); -/* Try to store VAL (or, if NULL_RTX, VALC) in LEN bytes starting at TO. - Return TRUE if successful, FALSE otherwise. TO is assumed to be - aligned at an ALIGN-bits boundary. LEN must be a multiple of - 1<decl)) != ARRAY_TYPE) + { + t = build2 (MODIFY_EXPR, targetm.fn_abi_va_list (cfun->decl), dst, src); + TREE_SIDE_EFFECTS (t) = 1; + expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); + } + else + { + rtx dstb, srcb, size; - The strategy is to issue one store_by_pieces for each power of two, - from most to least significant, guarded by a test on whether there - are at least that many bytes left to copy in LEN. + /* Evaluate to pointers. */ + dstb = expand_expr (dst, NULL_RTX, Pmode, EXPAND_NORMAL); + srcb = expand_expr (src, NULL_RTX, Pmode, EXPAND_NORMAL); + size = expand_expr (TYPE_SIZE_UNIT (targetm.fn_abi_va_list (cfun->decl)), + NULL_RTX, VOIDmode, EXPAND_NORMAL); - ??? Should we skip some powers of two in favor of loops? Maybe start - at the max of TO/LEN/word alignment, at least when optimizing for - size, instead of ensuring O(log len) dynamic compares? */ + dstb = convert_memory_address (Pmode, dstb); + srcb = convert_memory_address (Pmode, srcb); -bool -try_store_by_multiple_pieces (rtx to, rtx len, unsigned int ctz_len, - unsigned HOST_WIDE_INT min_len, - unsigned HOST_WIDE_INT max_len, - rtx val, char valc, unsigned int align) -{ - int max_bits = floor_log2 (max_len); - int min_bits = floor_log2 (min_len); - int sctz_len = ctz_len; + /* "Dereference" to BLKmode memories. */ + dstb = gen_rtx_MEM (BLKmode, dstb); + set_mem_alias_set (dstb, get_alias_set (TREE_TYPE (TREE_TYPE (dst)))); + set_mem_align (dstb, TYPE_ALIGN (targetm.fn_abi_va_list (cfun->decl))); + srcb = gen_rtx_MEM (BLKmode, srcb); + set_mem_alias_set (srcb, get_alias_set (TREE_TYPE (TREE_TYPE (src)))); + set_mem_align (srcb, TYPE_ALIGN (targetm.fn_abi_va_list (cfun->decl))); - gcc_checking_assert (sctz_len >= 0); + /* Copy. */ + emit_block_move (dstb, srcb, size, BLOCK_OP_NORMAL); + } - if (val) - valc = 1; + return const0_rtx; +} - /* Bits more significant than TST_BITS are part of the shared prefix - in the binary representation of both min_len and max_len. Since - they're identical, we don't need to test them in the loop. */ - int tst_bits = (max_bits != min_bits ? max_bits - : floor_log2 (max_len ^ min_len)); +/* Expand a call to one of the builtin functions __builtin_frame_address or + __builtin_return_address. */ - /* Check whether it's profitable to start by storing a fixed BLKSIZE - bytes, to lower max_bits. In the unlikely case of a constant LEN - (implied by identical MAX_LEN and MIN_LEN), we want to issue a - single store_by_pieces, but otherwise, select the minimum multiple - of the ALIGN (in bytes) and of the MCD of the possible LENs, that - brings MAX_LEN below TST_BITS, if that's lower than min_len. */ - unsigned HOST_WIDE_INT blksize; - if (max_len > min_len) +static rtx +expand_builtin_frame_address (tree fndecl, tree exp) +{ + /* The argument must be a nonnegative integer constant. + It counts the number of frames to scan up the stack. + The value is either the frame pointer value or the return + address saved in that frame. */ + if (call_expr_nargs (exp) == 0) + /* Warning about missing arg was already issued. */ + return const0_rtx; + else if (! tree_fits_uhwi_p (CALL_EXPR_ARG (exp, 0))) { - unsigned HOST_WIDE_INT alrng = MAX (HOST_WIDE_INT_1U << ctz_len, - align / BITS_PER_UNIT); - blksize = max_len - (HOST_WIDE_INT_1U << tst_bits) + alrng; - blksize &= ~(alrng - 1); + error ("invalid argument to %qD", fndecl); + return const0_rtx; } - else if (max_len == min_len) - blksize = max_len; else - gcc_unreachable (); - if (min_len >= blksize) { - min_len -= blksize; - min_bits = floor_log2 (min_len); - max_len -= blksize; - max_bits = floor_log2 (max_len); - - tst_bits = (max_bits != min_bits ? max_bits - : floor_log2 (max_len ^ min_len)); - } - else - blksize = 0; + /* Number of frames to scan up the stack. */ + unsigned HOST_WIDE_INT count = tree_to_uhwi (CALL_EXPR_ARG (exp, 0)); - /* Check that we can use store by pieces for the maximum store count - we may issue (initial fixed-size block, plus conditional - power-of-two-sized from max_bits to ctz_len. */ - unsigned HOST_WIDE_INT xlenest = blksize; - if (max_bits >= 0) - xlenest += ((HOST_WIDE_INT_1U << max_bits) * 2 - - (HOST_WIDE_INT_1U << ctz_len)); - if (!can_store_by_pieces (xlenest, builtin_memset_read_str, - &valc, align, true)) - return false; + rtx tem = expand_builtin_return_addr (DECL_FUNCTION_CODE (fndecl), count); - rtx (*constfun) (void *, void *, HOST_WIDE_INT, scalar_int_mode); - void *constfundata; - if (val) - { - constfun = builtin_memset_gen_str; - constfundata = val = force_reg (TYPE_MODE (unsigned_char_type_node), - val); - } - else - { - constfun = builtin_memset_read_str; - constfundata = &valc; - } + /* Some ports cannot access arbitrary stack frames. */ + if (tem == NULL) + { + warning (0, "unsupported argument to %qD", fndecl); + return const0_rtx; + } - rtx ptr = copy_addr_to_reg (convert_to_mode (ptr_mode, XEXP (to, 0), 0)); - rtx rem = copy_to_mode_reg (ptr_mode, convert_to_mode (ptr_mode, len, 0)); - to = replace_equiv_address (to, ptr); - set_mem_align (to, align); + if (count) + { + /* Warn since no effort is made to ensure that any frame + beyond the current one exists or can be safely reached. */ + warning (OPT_Wframe_address, "calling %qD with " + "a nonzero argument is unsafe", fndecl); + } - if (blksize) - { - to = store_by_pieces (to, blksize, - constfun, constfundata, - align, true, - max_len != 0 ? RETURN_END : RETURN_BEGIN); - if (max_len == 0) - return true; + /* For __builtin_frame_address, return what we've got. */ + if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_FRAME_ADDRESS) + return tem; - /* Adjust PTR, TO and REM. Since TO's address is likely - PTR+offset, we have to replace it. */ - emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX)); - to = replace_equiv_address (to, ptr); - rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize); - emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX)); + if (!REG_P (tem) + && ! CONSTANT_P (tem)) + tem = copy_addr_to_reg (tem); + return tem; } +} - /* Iterate over power-of-two block sizes from the maximum length to - the least significant bit possibly set in the length. */ - for (int i = max_bits; i >= sctz_len; i--) - { - rtx_code_label *label = NULL; - blksize = HOST_WIDE_INT_1U << i; +/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we + failed and the caller should emit a normal call. */ - /* If we're past the bits shared between min_ and max_len, expand - a test on the dynamic length, comparing it with the - BLKSIZE. */ - if (i <= tst_bits) - { - label = gen_label_rtx (); - emit_cmp_and_jump_insns (rem, GEN_INT (blksize), LT, NULL, - ptr_mode, 1, label, - profile_probability::even ()); - } - /* If we are at a bit that is in the prefix shared by min_ and - max_len, skip this BLKSIZE if the bit is clear. */ - else if ((max_len & blksize) == 0) - continue; +static rtx +expand_builtin_alloca (tree exp) +{ + rtx op0; + rtx result; + unsigned int align; + tree fndecl = get_callee_fndecl (exp); + HOST_WIDE_INT max_size; + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + bool alloca_for_var = CALL_ALLOCA_FOR_VAR_P (exp); + bool valid_arglist + = (fcode == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX + ? validate_arglist (exp, INTEGER_TYPE, INTEGER_TYPE, INTEGER_TYPE, + VOID_TYPE) + : fcode == BUILT_IN_ALLOCA_WITH_ALIGN + ? validate_arglist (exp, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE) + : validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)); - /* Issue a store of BLKSIZE bytes. */ - to = store_by_pieces (to, blksize, - constfun, constfundata, - align, true, - i != sctz_len ? RETURN_END : RETURN_BEGIN); + if (!valid_arglist) + return NULL_RTX; - /* Adjust REM and PTR, unless this is the last iteration. */ - if (i != sctz_len) - { - emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX)); - to = replace_equiv_address (to, ptr); - rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize); - emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX)); - } + if ((alloca_for_var + && warn_vla_limit >= HOST_WIDE_INT_MAX + && warn_alloc_size_limit < warn_vla_limit) + || (!alloca_for_var + && warn_alloca_limit >= HOST_WIDE_INT_MAX + && warn_alloc_size_limit < warn_alloca_limit + )) + { + /* -Walloca-larger-than and -Wvla-larger-than settings of + less than HOST_WIDE_INT_MAX override the more general + -Walloc-size-larger-than so unless either of the former + options is smaller than the last one (wchich would imply + that the call was already checked), check the alloca + arguments for overflow. */ + tree args[] = { CALL_EXPR_ARG (exp, 0), NULL_TREE }; + int idx[] = { 0, -1 }; + maybe_warn_alloc_args_overflow (fndecl, exp, args, idx); + } - if (label) - { - emit_label (label); + /* Compute the argument. */ + op0 = expand_normal (CALL_EXPR_ARG (exp, 0)); - /* Given conditional stores, the offset can no longer be - known, so clear it. */ - clear_mem_offset (to); - } - } + /* Compute the alignment. */ + align = (fcode == BUILT_IN_ALLOCA + ? BIGGEST_ALIGNMENT + : TREE_INT_CST_LOW (CALL_EXPR_ARG (exp, 1))); - return true; + /* Compute the maximum size. */ + max_size = (fcode == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX + ? TREE_INT_CST_LOW (CALL_EXPR_ARG (exp, 2)) + : -1); + + /* Allocate the desired space. If the allocation stems from the declaration + of a variable-sized object, it cannot accumulate. */ + result + = allocate_dynamic_stack_space (op0, 0, align, max_size, alloca_for_var); + result = convert_memory_address (ptr_mode, result); + + /* Dynamic allocations for variables are recorded during gimplification. */ + if (!alloca_for_var && (flag_callgraph_info & CALLGRAPH_INFO_DYNAMIC_ALLOC)) + record_dynamic_alloc (exp); + + return result; } -/* Helper function to do the actual work for expand_builtin_memset. The - arguments to the builtin_memset call DEST, VAL, and LEN are broken out - so that this can also be called without constructing an actual CALL_EXPR. - The other arguments and return value are the same as for - expand_builtin_memset. */ +/* Emit a call to __asan_allocas_unpoison call in EXP. Add to second argument + of the call virtual_stack_dynamic_rtx - stack_pointer_rtx, which is the + STACK_DYNAMIC_OFFSET value. See motivation for this in comment to + handle_builtin_stack_restore function. */ static rtx -expand_builtin_memset_args (tree dest, tree val, tree len, - rtx target, machine_mode mode, tree orig_exp) +expand_asan_emit_allocas_unpoison (tree exp) { - tree fndecl, fn; - enum built_in_function fcode; - machine_mode val_mode; - char c; - unsigned int dest_align; - rtx dest_mem, dest_addr, len_rtx; - HOST_WIDE_INT expected_size = -1; - unsigned int expected_align = 0; - unsigned HOST_WIDE_INT min_size; - unsigned HOST_WIDE_INT max_size; - unsigned HOST_WIDE_INT probable_max_size; + tree arg0 = CALL_EXPR_ARG (exp, 0); + tree arg1 = CALL_EXPR_ARG (exp, 1); + rtx top = expand_expr (arg0, NULL_RTX, ptr_mode, EXPAND_NORMAL); + rtx bot = expand_expr (arg1, NULL_RTX, ptr_mode, EXPAND_NORMAL); + rtx off = expand_simple_binop (Pmode, MINUS, virtual_stack_dynamic_rtx, + stack_pointer_rtx, NULL_RTX, 0, + OPTAB_LIB_WIDEN); + off = convert_modes (ptr_mode, Pmode, off, 0); + bot = expand_simple_binop (ptr_mode, PLUS, bot, off, NULL_RTX, 0, + OPTAB_LIB_WIDEN); + rtx ret = init_one_libfunc ("__asan_allocas_unpoison"); + ret = emit_library_call_value (ret, NULL_RTX, LCT_NORMAL, ptr_mode, + top, ptr_mode, bot, ptr_mode); + return ret; +} - dest_align = get_pointer_alignment (dest); +/* Expand a call to bswap builtin in EXP. + Return NULL_RTX if a normal call should be emitted rather than expanding the + function in-line. If convenient, the result should be placed in TARGET. + SUBTARGET may be used as the target for computing one of EXP's operands. */ - /* If DEST is not a pointer type, don't do this operation in-line. */ - if (dest_align == 0) +static rtx +expand_builtin_bswap (machine_mode target_mode, tree exp, rtx target, + rtx subtarget) +{ + tree arg; + rtx op0; + + if (!validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; - if (currently_expanding_gimple_stmt) - stringop_block_profile (currently_expanding_gimple_stmt, - &expected_align, &expected_size); + arg = CALL_EXPR_ARG (exp, 0); + op0 = expand_expr (arg, + subtarget && GET_MODE (subtarget) == target_mode + ? subtarget : NULL_RTX, + target_mode, EXPAND_NORMAL); + if (GET_MODE (op0) != target_mode) + op0 = convert_to_mode (target_mode, op0, 1); - if (expected_align < dest_align) - expected_align = dest_align; + target = expand_unop (target_mode, bswap_optab, op0, target, 1); - /* If the LEN parameter is zero, return DEST. */ - if (integer_zerop (len)) - { - /* Evaluate and ignore VAL in case it has side-effects. */ - expand_expr (val, const0_rtx, VOIDmode, EXPAND_NORMAL); - return expand_expr (dest, target, mode, EXPAND_NORMAL); - } + gcc_assert (target); - /* Stabilize the arguments in case we fail. */ - dest = builtin_save_expr (dest); - val = builtin_save_expr (val); - len = builtin_save_expr (len); + return convert_to_mode (target_mode, target, 1); +} - len_rtx = expand_normal (len); - determine_block_size (len, len_rtx, &min_size, &max_size, - &probable_max_size); - dest_mem = get_memory_rtx (dest, len); - val_mode = TYPE_MODE (unsigned_char_type_node); +/* Expand a call to a unary builtin in EXP. + Return NULL_RTX if a normal call should be emitted rather than expanding the + function in-line. If convenient, the result should be placed in TARGET. + SUBTARGET may be used as the target for computing one of EXP's operands. */ - if (TREE_CODE (val) != INTEGER_CST - || target_char_cast (val, &c)) - { - rtx val_rtx; +static rtx +expand_builtin_unop (machine_mode target_mode, tree exp, rtx target, + rtx subtarget, optab op_optab) +{ + rtx op0; - val_rtx = expand_normal (val); - val_rtx = convert_to_mode (val_mode, val_rtx, 0); + if (!validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - /* Assume that we can memset by pieces if we can store - * the coefficients by pieces (in the required modes). - * We can't pass builtin_memset_gen_str as that emits RTL. */ - c = 1; - if (tree_fits_uhwi_p (len) - && can_store_by_pieces (tree_to_uhwi (len), - builtin_memset_read_str, &c, dest_align, - true)) - { - val_rtx = force_reg (val_mode, val_rtx); - store_by_pieces (dest_mem, tree_to_uhwi (len), - builtin_memset_gen_str, val_rtx, dest_align, - true, RETURN_BEGIN); - } - else if (!set_storage_via_setmem (dest_mem, len_rtx, val_rtx, - dest_align, expected_align, - expected_size, min_size, max_size, - probable_max_size) - && !try_store_by_multiple_pieces (dest_mem, len_rtx, - tree_ctz (len), - min_size, max_size, - val_rtx, 0, - dest_align)) - goto do_libcall; + /* Compute the argument. */ + op0 = expand_expr (CALL_EXPR_ARG (exp, 0), + (subtarget + && (TYPE_MODE (TREE_TYPE (CALL_EXPR_ARG (exp, 0))) + == GET_MODE (subtarget))) ? subtarget : NULL_RTX, + VOIDmode, EXPAND_NORMAL); + /* Compute op, into TARGET if possible. + Set TARGET to wherever the result comes back. */ + target = expand_unop (TYPE_MODE (TREE_TYPE (CALL_EXPR_ARG (exp, 0))), + op_optab, op0, target, op_optab != clrsb_optab); + gcc_assert (target); - dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX); - dest_mem = convert_memory_address (ptr_mode, dest_mem); - return dest_mem; - } + return convert_to_mode (target_mode, target, 0); +} - if (c) - { - if (tree_fits_uhwi_p (len) - && can_store_by_pieces (tree_to_uhwi (len), - builtin_memset_read_str, &c, dest_align, - true)) - store_by_pieces (dest_mem, tree_to_uhwi (len), - builtin_memset_read_str, &c, dest_align, true, - RETURN_BEGIN); - else if (!set_storage_via_setmem (dest_mem, len_rtx, - gen_int_mode (c, val_mode), - dest_align, expected_align, - expected_size, min_size, max_size, - probable_max_size) - && !try_store_by_multiple_pieces (dest_mem, len_rtx, - tree_ctz (len), - min_size, max_size, - NULL_RTX, c, - dest_align)) - goto do_libcall; +/* Expand a call to __builtin_expect. We just return our argument + as the builtin_expect semantic should've been already executed by + tree branch prediction pass. */ - dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX); - dest_mem = convert_memory_address (ptr_mode, dest_mem); - return dest_mem; - } +static rtx +expand_builtin_expect (tree exp, rtx target) +{ + tree arg; - set_mem_align (dest_mem, dest_align); - dest_addr = clear_storage_hints (dest_mem, len_rtx, - CALL_EXPR_TAILCALL (orig_exp) - ? BLOCK_OP_TAILCALL : BLOCK_OP_NORMAL, - expected_align, expected_size, - min_size, max_size, - probable_max_size, tree_ctz (len)); + if (call_expr_nargs (exp) < 2) + return const0_rtx; + arg = CALL_EXPR_ARG (exp, 0); - if (dest_addr == 0) - { - dest_addr = force_operand (XEXP (dest_mem, 0), NULL_RTX); - dest_addr = convert_memory_address (ptr_mode, dest_addr); - } + target = expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); + /* When guessing was done, the hints should be already stripped away. */ + gcc_assert (!flag_guess_branch_prob + || optimize == 0 || seen_error ()); + return target; +} - return dest_addr; +/* Expand a call to __builtin_expect_with_probability. We just return our + argument as the builtin_expect semantic should've been already executed by + tree branch prediction pass. */ - do_libcall: - fndecl = get_callee_fndecl (orig_exp); - fcode = DECL_FUNCTION_CODE (fndecl); - if (fcode == BUILT_IN_MEMSET) - fn = build_call_nofold_loc (EXPR_LOCATION (orig_exp), fndecl, 3, - dest, val, len); - else if (fcode == BUILT_IN_BZERO) - fn = build_call_nofold_loc (EXPR_LOCATION (orig_exp), fndecl, 2, - dest, len); - else - gcc_unreachable (); - gcc_assert (TREE_CODE (fn) == CALL_EXPR); - CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (orig_exp); - return expand_call (fn, target, target == const0_rtx); +static rtx +expand_builtin_expect_with_probability (tree exp, rtx target) +{ + tree arg; + + if (call_expr_nargs (exp) < 3) + return const0_rtx; + arg = CALL_EXPR_ARG (exp, 0); + + target = expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); + /* When guessing was done, the hints should be already stripped away. */ + gcc_assert (!flag_guess_branch_prob + || optimize == 0 || seen_error ()); + return target; } -/* Expand expression EXP, which is a call to the bzero builtin. Return - NULL_RTX if we failed the caller should emit a normal call. */ + +/* Expand a call to __builtin_assume_aligned. We just return our first + argument as the builtin_assume_aligned semantic should've been already + executed by CCP. */ static rtx -expand_builtin_bzero (tree exp) +expand_builtin_assume_aligned (tree exp, rtx target) { - if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; - - tree dest = CALL_EXPR_ARG (exp, 0); - tree size = CALL_EXPR_ARG (exp, 1); + if (call_expr_nargs (exp) < 2) + return const0_rtx; + target = expand_expr (CALL_EXPR_ARG (exp, 0), target, VOIDmode, + EXPAND_NORMAL); + gcc_assert (!TREE_SIDE_EFFECTS (CALL_EXPR_ARG (exp, 1)) + && (call_expr_nargs (exp) < 3 + || !TREE_SIDE_EFFECTS (CALL_EXPR_ARG (exp, 2)))); + return target; +} - check_memop_access (exp, dest, NULL_TREE, size); +void +expand_builtin_trap (void) +{ + if (targetm.have_trap ()) + { + rtx_insn *insn = emit_insn (targetm.gen_trap ()); + /* For trap insns when not accumulating outgoing args force + REG_ARGS_SIZE note to prevent crossjumping of calls with + different args sizes. */ + if (!ACCUMULATE_OUTGOING_ARGS) + add_args_size_note (insn, stack_pointer_delta); + } + else + { + tree fn = builtin_decl_implicit (BUILT_IN_ABORT); + tree call_expr = build_call_expr (fn, 0); + expand_call (call_expr, NULL_RTX, false); + } - /* New argument list transforming bzero(ptr x, int y) to - memset(ptr x, int 0, size_t y). This is done this way - so that if it isn't expanded inline, we fallback to - calling bzero instead of memset. */ + emit_barrier (); +} - location_t loc = EXPR_LOCATION (exp); +/* Expand a call to __builtin_unreachable. We do nothing except emit + a barrier saying that control flow will not pass here. - return expand_builtin_memset_args (dest, integer_zero_node, - fold_convert_loc (loc, - size_type_node, size), - const0_rtx, VOIDmode, exp); + It is the responsibility of the program being compiled to ensure + that control flow does never reach __builtin_unreachable. */ +static void +expand_builtin_unreachable (void) +{ + emit_barrier (); } -/* Try to expand cmpstr operation ICODE with the given operands. - Return the result rtx on success, otherwise return null. */ +/* Expand EXP, a call to fabs, fabsf or fabsl. + Return NULL_RTX if a normal call should be emitted rather than expanding + the function inline. If convenient, the result should be placed + in TARGET. SUBTARGET may be used as the target for computing + the operand. */ static rtx -expand_cmpstr (insn_code icode, rtx target, rtx arg1_rtx, rtx arg2_rtx, - HOST_WIDE_INT align) +expand_builtin_fabs (tree exp, rtx target, rtx subtarget) { - machine_mode insn_mode = insn_data[icode].operand[0].mode; + machine_mode mode; + tree arg; + rtx op0; - if (target && (!REG_P (target) || HARD_REGISTER_P (target))) - target = NULL_RTX; + if (!validate_arglist (exp, REAL_TYPE, VOID_TYPE)) + return NULL_RTX; - class expand_operand ops[4]; - create_output_operand (&ops[0], target, insn_mode); - create_fixed_operand (&ops[1], arg1_rtx); - create_fixed_operand (&ops[2], arg2_rtx); - create_integer_operand (&ops[3], align); - if (maybe_expand_insn (icode, 4, ops)) - return ops[0].value; - return NULL_RTX; + arg = CALL_EXPR_ARG (exp, 0); + CALL_EXPR_ARG (exp, 0) = arg = builtin_save_expr (arg); + mode = TYPE_MODE (TREE_TYPE (arg)); + op0 = expand_expr (arg, subtarget, VOIDmode, EXPAND_NORMAL); + return expand_abs (mode, op0, target, 0, safe_from_p (target, arg, 1)); } -/* Expand expression EXP, which is a call to the memcmp built-in function. - Return NULL_RTX if we failed and the caller should emit a normal call, - otherwise try to get the result in TARGET, if convenient. - RESULT_EQ is true if we can relax the returned value to be either zero - or nonzero, without caring about the sign. */ +/* Expand EXP, a call to copysign, copysignf, or copysignl. + Return NULL is a normal call should be emitted rather than expanding the + function inline. If convenient, the result should be placed in TARGET. + SUBTARGET may be used as the target for computing the operand. */ static rtx -expand_builtin_memcmp (tree exp, rtx target, bool result_eq) +expand_builtin_copysign (tree exp, rtx target, rtx subtarget) { - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + rtx op0, op1; + tree arg; + + if (!validate_arglist (exp, REAL_TYPE, REAL_TYPE, VOID_TYPE)) return NULL_RTX; - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); + arg = CALL_EXPR_ARG (exp, 0); + op0 = expand_expr (arg, subtarget, VOIDmode, EXPAND_NORMAL); - /* Diagnose calls where the specified length exceeds the size of either - object. */ - if (!check_read_access (exp, arg1, len, 0) - || !check_read_access (exp, arg2, len, 0)) - return NULL_RTX; + arg = CALL_EXPR_ARG (exp, 1); + op1 = expand_normal (arg); - /* Due to the performance benefit, always inline the calls first - when result_eq is false. */ - rtx result = NULL_RTX; - enum built_in_function fcode = DECL_FUNCTION_CODE (get_callee_fndecl (exp)); - if (!result_eq && fcode != BUILT_IN_BCMP) - { - result = inline_expand_builtin_bytecmp (exp, target); - if (result) - return result; - } + return expand_copysign (op0, op1, target); +} - machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); - location_t loc = EXPR_LOCATION (exp); +/* Emit a call to __builtin___clear_cache. */ - unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; - unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; +void +default_emit_call_builtin___clear_cache (rtx begin, rtx end) +{ + rtx callee = gen_rtx_SYMBOL_REF (Pmode, + BUILTIN_ASM_NAME_PTR + (BUILT_IN_CLEAR_CACHE)); - /* If we don't have POINTER_TYPE, call the function. */ - if (arg1_align == 0 || arg2_align == 0) - return NULL_RTX; + emit_library_call (callee, + LCT_NORMAL, VOIDmode, + convert_memory_address (ptr_mode, begin), ptr_mode, + convert_memory_address (ptr_mode, end), ptr_mode); +} - rtx arg1_rtx = get_memory_rtx (arg1, len); - rtx arg2_rtx = get_memory_rtx (arg2, len); - rtx len_rtx = expand_normal (fold_convert_loc (loc, sizetype, len)); +/* Emit a call to __builtin___clear_cache, unless the target specifies + it as do-nothing. This function can be used by trampoline + finalizers to duplicate the effects of expanding a call to the + clear_cache builtin. */ - /* Set MEM_SIZE as appropriate. */ - if (CONST_INT_P (len_rtx)) +void +maybe_emit_call_builtin___clear_cache (rtx begin, rtx end) +{ + if ((GET_MODE (begin) != ptr_mode && GET_MODE (begin) != Pmode) + || (GET_MODE (end) != ptr_mode && GET_MODE (end) != Pmode)) { - set_mem_size (arg1_rtx, INTVAL (len_rtx)); - set_mem_size (arg2_rtx, INTVAL (len_rtx)); + error ("both arguments to %<__builtin___clear_cache%> must be pointers"); + return; } - by_pieces_constfn constfn = NULL; + if (targetm.have_clear_cache ()) + { + /* We have a "clear_cache" insn, and it will handle everything. */ + class expand_operand ops[2]; - /* Try to get the byte representation of the constant ARG2 (or, only - when the function's result is used for equality to zero, ARG1) - points to, with its byte size in NBYTES. */ - unsigned HOST_WIDE_INT nbytes; - const char *rep = getbyterep (arg2, &nbytes); - if (result_eq && rep == NULL) + create_address_operand (&ops[0], begin); + create_address_operand (&ops[1], end); + + if (maybe_expand_insn (targetm.code_for_clear_cache, 2, ops)) + return; + } + else { - /* For equality to zero the arguments are interchangeable. */ - rep = getbyterep (arg1, &nbytes); - if (rep != NULL) - std::swap (arg1_rtx, arg2_rtx); +#ifndef CLEAR_INSN_CACHE + /* There is no "clear_cache" insn, and __clear_cache() in libgcc + does nothing. There is no need to call it. Do nothing. */ + return; +#endif /* CLEAR_INSN_CACHE */ } - /* If the function's constant bound LEN_RTX is less than or equal - to the byte size of the representation of the constant argument, - and if block move would be done by pieces, we can avoid loading - the bytes from memory and only store the computed constant result. */ - if (rep - && CONST_INT_P (len_rtx) - && (unsigned HOST_WIDE_INT) INTVAL (len_rtx) <= nbytes) - constfn = builtin_memcpy_read_str; + targetm.calls.emit_call_builtin___clear_cache (begin, end); +} - result = emit_block_cmp_hints (arg1_rtx, arg2_rtx, len_rtx, - TREE_TYPE (len), target, - result_eq, constfn, - CONST_CAST (char *, rep)); +/* Expand a call to __builtin___clear_cache. */ - if (result) +static void +expand_builtin___clear_cache (tree exp) +{ + tree begin, end; + rtx begin_rtx, end_rtx; + + /* We must not expand to a library call. If we did, any + fallback library function in libgcc that might contain a call to + __builtin___clear_cache() would recurse infinitely. */ + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) { - /* Return the value in the proper mode for this function. */ - if (GET_MODE (result) == mode) - return result; + error ("both arguments to %<__builtin___clear_cache%> must be pointers"); + return; + } - if (target != 0) - { - convert_move (target, result, 0); - return target; - } + begin = CALL_EXPR_ARG (exp, 0); + begin_rtx = expand_expr (begin, NULL_RTX, Pmode, EXPAND_NORMAL); - return convert_to_mode (mode, result, 0); - } + end = CALL_EXPR_ARG (exp, 1); + end_rtx = expand_expr (end, NULL_RTX, Pmode, EXPAND_NORMAL); - return NULL_RTX; + maybe_emit_call_builtin___clear_cache (begin_rtx, end_rtx); } -/* Expand expression EXP, which is a call to the strcmp builtin. Return NULL_RTX - if we failed the caller should emit a normal call, otherwise try to get - the result in TARGET, if convenient. */ +/* Given a trampoline address, make sure it satisfies TRAMPOLINE_ALIGNMENT. */ static rtx -expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target) +round_trampoline_addr (rtx tramp) { - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - return NULL_RTX; + rtx temp, addend, mask; - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); + /* If we don't need too much alignment, we'll have been guaranteed + proper alignment by get_trampoline_type. */ + if (TRAMPOLINE_ALIGNMENT <= STACK_BOUNDARY) + return tramp; - if (!check_read_access (exp, arg1) - || !check_read_access (exp, arg2)) - return NULL_RTX; + /* Round address up to desired boundary. */ + temp = gen_reg_rtx (Pmode); + addend = gen_int_mode (TRAMPOLINE_ALIGNMENT / BITS_PER_UNIT - 1, Pmode); + mask = gen_int_mode (-TRAMPOLINE_ALIGNMENT / BITS_PER_UNIT, Pmode); - /* Due to the performance benefit, always inline the calls first. */ - rtx result = NULL_RTX; - result = inline_expand_builtin_bytecmp (exp, target); - if (result) - return result; + temp = expand_simple_binop (Pmode, PLUS, tramp, addend, + temp, 0, OPTAB_LIB_WIDEN); + tramp = expand_simple_binop (Pmode, AND, temp, mask, + temp, 0, OPTAB_LIB_WIDEN); - insn_code cmpstr_icode = direct_optab_handler (cmpstr_optab, SImode); - insn_code cmpstrn_icode = direct_optab_handler (cmpstrn_optab, SImode); - if (cmpstr_icode == CODE_FOR_nothing && cmpstrn_icode == CODE_FOR_nothing) - return NULL_RTX; + return tramp; +} - unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; - unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; +static rtx +expand_builtin_init_trampoline (tree exp, bool onstack) +{ + tree t_tramp, t_func, t_chain; + rtx m_tramp, r_tramp, r_chain, tmp; - /* If we don't have POINTER_TYPE, call the function. */ - if (arg1_align == 0 || arg2_align == 0) + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, + POINTER_TYPE, VOID_TYPE)) return NULL_RTX; - /* Stabilize the arguments in case gen_cmpstr(n)si fail. */ - arg1 = builtin_save_expr (arg1); - arg2 = builtin_save_expr (arg2); + t_tramp = CALL_EXPR_ARG (exp, 0); + t_func = CALL_EXPR_ARG (exp, 1); + t_chain = CALL_EXPR_ARG (exp, 2); - rtx arg1_rtx = get_memory_rtx (arg1, NULL); - rtx arg2_rtx = get_memory_rtx (arg2, NULL); + r_tramp = expand_normal (t_tramp); + m_tramp = gen_rtx_MEM (BLKmode, r_tramp); + MEM_NOTRAP_P (m_tramp) = 1; - /* Try to call cmpstrsi. */ - if (cmpstr_icode != CODE_FOR_nothing) - result = expand_cmpstr (cmpstr_icode, target, arg1_rtx, arg2_rtx, - MIN (arg1_align, arg2_align)); + /* If ONSTACK, the TRAMP argument should be the address of a field + within the local function's FRAME decl. Either way, let's see if + we can fill in the MEM_ATTRs for this memory. */ + if (TREE_CODE (t_tramp) == ADDR_EXPR) + set_mem_attributes (m_tramp, TREE_OPERAND (t_tramp, 0), true); - /* Try to determine at least one length and call cmpstrnsi. */ - if (!result && cmpstrn_icode != CODE_FOR_nothing) + /* Creator of a heap trampoline is responsible for making sure the + address is aligned to at least STACK_BOUNDARY. Normally malloc + will ensure this anyhow. */ + tmp = round_trampoline_addr (r_tramp); + if (tmp != r_tramp) { - tree len; - rtx arg3_rtx; - - tree len1 = c_strlen (arg1, 1); - tree len2 = c_strlen (arg2, 1); - - if (len1) - len1 = size_binop (PLUS_EXPR, ssize_int (1), len1); - if (len2) - len2 = size_binop (PLUS_EXPR, ssize_int (1), len2); + m_tramp = change_address (m_tramp, BLKmode, tmp); + set_mem_align (m_tramp, TRAMPOLINE_ALIGNMENT); + set_mem_size (m_tramp, TRAMPOLINE_SIZE); + } - /* If we don't have a constant length for the first, use the length - of the second, if we know it. We don't require a constant for - this case; some cost analysis could be done if both are available - but neither is constant. For now, assume they're equally cheap, - unless one has side effects. If both strings have constant lengths, - use the smaller. */ + /* The FUNC argument should be the address of the nested function. + Extract the actual function decl to pass to the hook. */ + gcc_assert (TREE_CODE (t_func) == ADDR_EXPR); + t_func = TREE_OPERAND (t_func, 0); + gcc_assert (TREE_CODE (t_func) == FUNCTION_DECL); - if (!len1) - len = len2; - else if (!len2) - len = len1; - else if (TREE_SIDE_EFFECTS (len1)) - len = len2; - else if (TREE_SIDE_EFFECTS (len2)) - len = len1; - else if (TREE_CODE (len1) != INTEGER_CST) - len = len2; - else if (TREE_CODE (len2) != INTEGER_CST) - len = len1; - else if (tree_int_cst_lt (len1, len2)) - len = len1; - else - len = len2; + r_chain = expand_normal (t_chain); - /* If both arguments have side effects, we cannot optimize. */ - if (len && !TREE_SIDE_EFFECTS (len)) - { - arg3_rtx = expand_normal (len); - result = expand_cmpstrn_or_cmpmem - (cmpstrn_icode, target, arg1_rtx, arg2_rtx, TREE_TYPE (len), - arg3_rtx, MIN (arg1_align, arg2_align)); - } - } + /* Generate insns to initialize the trampoline. */ + targetm.calls.trampoline_init (m_tramp, t_func, r_chain); - tree fndecl = get_callee_fndecl (exp); - if (result) + if (onstack) { - /* Check to see if the argument was declared attribute nonstring - and if so, issue a warning since at this point it's not known - to be nul-terminated. */ - maybe_warn_nonstring_arg (fndecl, exp); + trampolines_created = 1; - /* Return the value in the proper mode for this function. */ - machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); - if (GET_MODE (result) == mode) - return result; - if (target == 0) - return convert_to_mode (mode, result, 0); - convert_move (target, result, 0); - return target; + if (targetm.calls.custom_function_descriptors != 0) + warning_at (DECL_SOURCE_LOCATION (t_func), OPT_Wtrampolines, + "trampoline generated for nested function %qD", t_func); } - /* Expand the library call ourselves using a stabilized argument - list to avoid re-evaluating the function's arguments twice. */ - tree fn = build_call_nofold_loc (EXPR_LOCATION (exp), fndecl, 2, arg1, arg2); - gcc_assert (TREE_CODE (fn) == CALL_EXPR); - CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); - return expand_call (fn, target, target == const0_rtx); + return const0_rtx; } -/* Expand expression EXP, which is a call to the strncmp builtin. Return - NULL_RTX if we failed the caller should emit a normal call, otherwise - try to get the result in TARGET, if convenient. */ - static rtx -expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, - ATTRIBUTE_UNUSED machine_mode mode) +expand_builtin_adjust_trampoline (tree exp) { - if (!validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; - - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); - tree arg3 = CALL_EXPR_ARG (exp, 2); + rtx tramp; - if (!check_nul_terminated_array (exp, arg1, arg3) - || !check_nul_terminated_array (exp, arg2, arg3)) + if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) return NULL_RTX; - location_t loc = EXPR_LOCATION (exp); - tree len1 = c_strlen (arg1, 1); - tree len2 = c_strlen (arg2, 1); - - if (!len1 || !len2) - { - /* Check to see if the argument was declared attribute nonstring - and if so, issue a warning since at this point it's not known - to be nul-terminated. */ - if (!maybe_warn_nonstring_arg (get_callee_fndecl (exp), exp) - && !len1 && !len2) - { - /* A strncmp read is constrained not just by the bound but - also by the length of the shorter string. Specifying - a bound that's larger than the size of either array makes - no sense and is likely a bug. When the length of neither - of the two strings is known but the sizes of both of - the arrays they are stored in is, issue a warning if - the bound is larger than than the size of the larger - of the two arrays. */ + tramp = expand_normal (CALL_EXPR_ARG (exp, 0)); + tramp = round_trampoline_addr (tramp); + if (targetm.calls.trampoline_adjust_address) + tramp = targetm.calls.trampoline_adjust_address (tramp); - access_ref ref1 (arg3, true); - access_ref ref2 (arg3, true); + return tramp; +} - tree bndrng[2] = { NULL_TREE, NULL_TREE }; - get_size_range (arg3, bndrng, ref1.bndrng); +/* Expand a call to the builtin descriptor initialization routine. + A descriptor is made up of a couple of pointers to the static + chain and the code entry in this order. */ - tree size1 = compute_objsize (arg1, 1, &ref1); - tree size2 = compute_objsize (arg2, 1, &ref2); - tree func = get_callee_fndecl (exp); +static rtx +expand_builtin_init_descriptor (tree exp) +{ + tree t_descr, t_func, t_chain; + rtx m_descr, r_descr, r_func, r_chain; - if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0])) - { - offset_int rem1 = ref1.size_remaining (); - offset_int rem2 = ref2.size_remaining (); - if (rem1 == 0 || rem2 == 0) - maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, - bndrng, integer_zero_node); - else - { - offset_int maxrem = wi::max (rem1, rem2, UNSIGNED); - if (maxrem < wi::to_offset (bndrng[0])) - maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, - func, bndrng, - wide_int_to_tree (sizetype, maxrem)); - } - } - else if (bndrng[0] - && !integer_zerop (bndrng[0]) - && ((size1 && integer_zerop (size1)) - || (size2 && integer_zerop (size2)))) - maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, - bndrng, integer_zero_node); - } - } - - /* Due to the performance benefit, always inline the calls first. */ - rtx result = NULL_RTX; - result = inline_expand_builtin_bytecmp (exp, target); - if (result) - return result; - - /* If c_strlen can determine an expression for one of the string - lengths, and it doesn't have side effects, then emit cmpstrnsi - using length MIN(strlen(string)+1, arg3). */ - insn_code cmpstrn_icode = direct_optab_handler (cmpstrn_optab, SImode); - if (cmpstrn_icode == CODE_FOR_nothing) + if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, POINTER_TYPE, + VOID_TYPE)) return NULL_RTX; - tree len; - - unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; - unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; - - if (len1) - len1 = size_binop_loc (loc, PLUS_EXPR, ssize_int (1), len1); - if (len2) - len2 = size_binop_loc (loc, PLUS_EXPR, ssize_int (1), len2); - - tree len3 = fold_convert_loc (loc, sizetype, arg3); - - /* If we don't have a constant length for the first, use the length - of the second, if we know it. If neither string is constant length, - use the given length argument. We don't require a constant for - this case; some cost analysis could be done if both are available - but neither is constant. For now, assume they're equally cheap, - unless one has side effects. If both strings have constant lengths, - use the smaller. */ + t_descr = CALL_EXPR_ARG (exp, 0); + t_func = CALL_EXPR_ARG (exp, 1); + t_chain = CALL_EXPR_ARG (exp, 2); - if (!len1 && !len2) - len = len3; - else if (!len1) - len = len2; - else if (!len2) - len = len1; - else if (TREE_SIDE_EFFECTS (len1)) - len = len2; - else if (TREE_SIDE_EFFECTS (len2)) - len = len1; - else if (TREE_CODE (len1) != INTEGER_CST) - len = len2; - else if (TREE_CODE (len2) != INTEGER_CST) - len = len1; - else if (tree_int_cst_lt (len1, len2)) - len = len1; - else - len = len2; + r_descr = expand_normal (t_descr); + m_descr = gen_rtx_MEM (BLKmode, r_descr); + MEM_NOTRAP_P (m_descr) = 1; + set_mem_align (m_descr, GET_MODE_ALIGNMENT (ptr_mode)); - /* If we are not using the given length, we must incorporate it here. - The actual new length parameter will be MIN(len,arg3) in this case. */ - if (len != len3) - { - len = fold_convert_loc (loc, sizetype, len); - len = fold_build2_loc (loc, MIN_EXPR, TREE_TYPE (len), len, len3); - } - rtx arg1_rtx = get_memory_rtx (arg1, len); - rtx arg2_rtx = get_memory_rtx (arg2, len); - rtx arg3_rtx = expand_normal (len); - result = expand_cmpstrn_or_cmpmem (cmpstrn_icode, target, arg1_rtx, - arg2_rtx, TREE_TYPE (len), arg3_rtx, - MIN (arg1_align, arg2_align)); + r_func = expand_normal (t_func); + r_chain = expand_normal (t_chain); - tree fndecl = get_callee_fndecl (exp); - if (result) - { - /* Return the value in the proper mode for this function. */ - mode = TYPE_MODE (TREE_TYPE (exp)); - if (GET_MODE (result) == mode) - return result; - if (target == 0) - return convert_to_mode (mode, result, 0); - convert_move (target, result, 0); - return target; - } + /* Generate insns to initialize the descriptor. */ + emit_move_insn (adjust_address_nv (m_descr, ptr_mode, 0), r_chain); + emit_move_insn (adjust_address_nv (m_descr, ptr_mode, + POINTER_SIZE / BITS_PER_UNIT), r_func); - /* Expand the library call ourselves using a stabilized argument - list to avoid re-evaluating the function's arguments twice. */ - tree call = build_call_nofold_loc (loc, fndecl, 3, arg1, arg2, len); - copy_warning (call, exp); - gcc_assert (TREE_CODE (call) == CALL_EXPR); - CALL_EXPR_TAILCALL (call) = CALL_EXPR_TAILCALL (exp); - return expand_call (call, target, target == const0_rtx); + return const0_rtx; } -/* Expand a call to __builtin_saveregs, generating the result in TARGET, - if that's convenient. */ +/* Expand a call to the builtin descriptor adjustment routine. */ -rtx -expand_builtin_saveregs (void) +static rtx +expand_builtin_adjust_descriptor (tree exp) { - rtx val; - rtx_insn *seq; - - /* Don't do __builtin_saveregs more than once in a function. - Save the result of the first call and reuse it. */ - if (saveregs_value != 0) - return saveregs_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. */ - - start_sequence (); - - /* Do whatever the machine needs done in this case. */ - val = targetm.calls.expand_builtin_saveregs (); + rtx tramp; - seq = get_insns (); - end_sequence (); + if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + return NULL_RTX; - saveregs_value = val; + tramp = expand_normal (CALL_EXPR_ARG (exp, 0)); - /* 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. */ - push_topmost_sequence (); - emit_insn_after (seq, entry_of_function ()); - pop_topmost_sequence (); + /* Unalign the descriptor to allow runtime identification. */ + tramp = plus_constant (ptr_mode, tramp, + targetm.calls.custom_function_descriptors); - return val; + return force_operand (tramp, NULL_RTX); } -/* Expand a call to __builtin_next_arg. */ - +/* Expand the call EXP to the built-in signbit, signbitf or signbitl + function. The function first checks whether the back end provides + an insn to implement signbit for the respective mode. If not, it + checks whether the floating point format of the value is such that + the sign bit can be extracted. If that is not the case, error out. + EXP is the expression that is a call to the builtin function; if + convenient, the result should be placed in TARGET. */ static rtx -expand_builtin_next_arg (void) +expand_builtin_signbit (tree exp, rtx target) { - /* Checking arguments is already done in fold_builtin_next_arg - that must be called before this function. */ - return expand_binop (ptr_mode, add_optab, - crtl->args.internal_arg_pointer, - crtl->args.arg_offset_rtx, - NULL_RTX, 0, OPTAB_LIB_WIDEN); -} + const struct real_format *fmt; + scalar_float_mode fmode; + scalar_int_mode rmode, imode; + tree arg; + int word, bitpos; + enum insn_code icode; + rtx temp; + location_t loc = EXPR_LOCATION (exp); -/* Make it easier for the backends by protecting the valist argument - from multiple evaluations. */ + if (!validate_arglist (exp, REAL_TYPE, VOID_TYPE)) + return NULL_RTX; -static tree -stabilize_va_list_loc (location_t loc, tree valist, int needs_lvalue) -{ - tree vatype = targetm.canonical_va_list_type (TREE_TYPE (valist)); + arg = CALL_EXPR_ARG (exp, 0); + fmode = SCALAR_FLOAT_TYPE_MODE (TREE_TYPE (arg)); + rmode = SCALAR_INT_TYPE_MODE (TREE_TYPE (exp)); + fmt = REAL_MODE_FORMAT (fmode); - /* The current way of determining the type of valist is completely - bogus. We should have the information on the va builtin instead. */ - if (!vatype) - vatype = targetm.fn_abi_va_list (cfun->decl); + arg = builtin_save_expr (arg); - if (TREE_CODE (vatype) == ARRAY_TYPE) + /* Expand the argument yielding a RTX expression. */ + temp = expand_normal (arg); + + /* Check if the back end provides an insn that handles signbit for the + argument's mode. */ + icode = optab_handler (signbit_optab, fmode); + if (icode != CODE_FOR_nothing) { - if (TREE_SIDE_EFFECTS (valist)) - valist = save_expr (valist); + rtx_insn *last = get_last_insn (); + target = gen_reg_rtx (TYPE_MODE (TREE_TYPE (exp))); + if (maybe_emit_unop_insn (icode, target, temp, UNKNOWN)) + return target; + delete_insns_since (last); + } - /* For this case, the backends will be expecting a pointer to - vatype, but it's possible we've actually been given an array - (an actual TARGET_CANONICAL_VA_LIST_TYPE (valist)). - So fix it. */ - if (TREE_CODE (TREE_TYPE (valist)) == ARRAY_TYPE) - { - tree p1 = build_pointer_type (TREE_TYPE (vatype)); - valist = build_fold_addr_expr_with_type_loc (loc, valist, p1); - } + /* For floating point formats without a sign bit, implement signbit + as "ARG < 0.0". */ + bitpos = fmt->signbit_ro; + if (bitpos < 0) + { + /* But we can't do this if the format supports signed zero. */ + gcc_assert (!fmt->has_signed_zero || !HONOR_SIGNED_ZEROS (fmode)); + + arg = fold_build2_loc (loc, LT_EXPR, TREE_TYPE (exp), arg, + build_real (TREE_TYPE (arg), dconst0)); + return expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); + } + + if (GET_MODE_SIZE (fmode) <= UNITS_PER_WORD) + { + imode = int_mode_for_mode (fmode).require (); + temp = gen_lowpart (imode, temp); } else { - tree pt = build_pointer_type (vatype); + imode = word_mode; + /* Handle targets with different FP word orders. */ + if (FLOAT_WORDS_BIG_ENDIAN) + word = (GET_MODE_BITSIZE (fmode) - bitpos) / BITS_PER_WORD; + else + word = bitpos / BITS_PER_WORD; + temp = operand_subword_force (temp, word, fmode); + bitpos = bitpos % BITS_PER_WORD; + } - if (! needs_lvalue) - { - if (! TREE_SIDE_EFFECTS (valist)) - return valist; + /* Force the intermediate word_mode (or narrower) result into a + register. This avoids attempting to create paradoxical SUBREGs + of floating point modes below. */ + temp = force_reg (imode, temp); - valist = fold_build1_loc (loc, ADDR_EXPR, pt, valist); - TREE_SIDE_EFFECTS (valist) = 1; - } + /* If the bitpos is within the "result mode" lowpart, the operation + can be implement with a single bitwise AND. Otherwise, we need + a right shift and an AND. */ - if (TREE_SIDE_EFFECTS (valist)) - valist = save_expr (valist); - valist = fold_build2_loc (loc, MEM_REF, - vatype, valist, build_int_cst (pt, 0)); + if (bitpos < GET_MODE_BITSIZE (rmode)) + { + wide_int mask = wi::set_bit_in_zero (bitpos, GET_MODE_PRECISION (rmode)); + + if (GET_MODE_SIZE (imode) > GET_MODE_SIZE (rmode)) + temp = gen_lowpart (rmode, temp); + temp = expand_binop (rmode, and_optab, temp, + immed_wide_int_const (mask, rmode), + NULL_RTX, 1, OPTAB_LIB_WIDEN); + } + else + { + /* Perform a logical right shift to place the signbit in the least + significant bit, then truncate the result to the desired mode + and mask just this bit. */ + temp = expand_shift (RSHIFT_EXPR, imode, temp, bitpos, NULL_RTX, 1); + temp = gen_lowpart (rmode, temp); + temp = expand_binop (rmode, and_optab, temp, const1_rtx, + NULL_RTX, 1, OPTAB_LIB_WIDEN); } - return valist; + return temp; } -/* The "standard" definition of va_list is void*. */ +/* Expand fork or exec calls. TARGET is the desired target of the + call. EXP is the call. FN is the + identificator of the actual function. IGNORE is nonzero if the + value is to be ignored. */ -tree -std_build_builtin_va_list (void) +static rtx +expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore) { - return ptr_type_node; -} + tree id, decl; + tree call; -/* The "standard" abi va_list is va_list_type_node. */ + if (DECL_FUNCTION_CODE (fn) != BUILT_IN_FORK) + { + tree path = CALL_EXPR_ARG (exp, 0); + /* Detect unterminated path. */ + if (!check_read_access (exp, path)) + return NULL_RTX; -tree -std_fn_abi_va_list (tree fndecl ATTRIBUTE_UNUSED) -{ - return va_list_type_node; -} + /* Also detect unterminated first argument. */ + switch (DECL_FUNCTION_CODE (fn)) + { + case BUILT_IN_EXECL: + case BUILT_IN_EXECLE: + case BUILT_IN_EXECLP: + if (!check_read_access (exp, path)) + return NULL_RTX; + default: + break; + } + } -/* The "standard" type of va_list is va_list_type_node. */ -tree -std_canonical_va_list_type (tree type) -{ - tree wtype, htype; + /* If we are not profiling, just call the function. */ + if (!profile_arc_flag) + return NULL_RTX; - wtype = va_list_type_node; - htype = type; + /* Otherwise call the wrapper. This should be equivalent for the rest of + compiler, so the code does not diverge, and the wrapper may run the + code necessary for keeping the profiling sane. */ - if (TREE_CODE (wtype) == ARRAY_TYPE) + switch (DECL_FUNCTION_CODE (fn)) { - /* If va_list is an array type, the argument may have decayed - to a pointer type, e.g. by being passed to another function. - In that case, unwrap both types so that we can compare the - underlying records. */ - if (TREE_CODE (htype) == ARRAY_TYPE - || POINTER_TYPE_P (htype)) - { - wtype = TREE_TYPE (wtype); - htype = TREE_TYPE (htype); - } - } - if (TYPE_MAIN_VARIANT (wtype) == TYPE_MAIN_VARIANT (htype)) - return va_list_type_node; + case BUILT_IN_FORK: + id = get_identifier ("__gcov_fork"); + break; - return NULL_TREE; -} + case BUILT_IN_EXECL: + id = get_identifier ("__gcov_execl"); + break; -/* The "standard" implementation of va_start: just assign `nextarg' to - the variable. */ + case BUILT_IN_EXECV: + id = get_identifier ("__gcov_execv"); + break; -void -std_expand_builtin_va_start (tree valist, rtx nextarg) -{ - rtx va_r = expand_expr (valist, NULL_RTX, VOIDmode, EXPAND_WRITE); - convert_move (va_r, nextarg, 0); -} + case BUILT_IN_EXECLP: + id = get_identifier ("__gcov_execlp"); + break; -/* Expand EXP, a call to __builtin_va_start. */ + case BUILT_IN_EXECLE: + id = get_identifier ("__gcov_execle"); + break; -static rtx -expand_builtin_va_start (tree exp) -{ - rtx nextarg; - tree valist; - location_t loc = EXPR_LOCATION (exp); + case BUILT_IN_EXECVP: + id = get_identifier ("__gcov_execvp"); + break; - if (call_expr_nargs (exp) < 2) - { - error_at (loc, "too few arguments to function %"); - return const0_rtx; - } + case BUILT_IN_EXECVE: + id = get_identifier ("__gcov_execve"); + break; - if (fold_builtin_next_arg (exp, true)) - return const0_rtx; + default: + gcc_unreachable (); + } - nextarg = expand_builtin_next_arg (); - valist = stabilize_va_list_loc (loc, CALL_EXPR_ARG (exp, 0), 1); + decl = build_decl (DECL_SOURCE_LOCATION (fn), + FUNCTION_DECL, id, TREE_TYPE (fn)); + DECL_EXTERNAL (decl) = 1; + TREE_PUBLIC (decl) = 1; + DECL_ARTIFICIAL (decl) = 1; + TREE_NOTHROW (decl) = 1; + DECL_VISIBILITY (decl) = VISIBILITY_DEFAULT; + DECL_VISIBILITY_SPECIFIED (decl) = 1; + call = rewrite_call_expr (EXPR_LOCATION (exp), exp, 0, decl, 0); + return expand_call (call, target, ignore); + } - if (targetm.expand_builtin_va_start) - targetm.expand_builtin_va_start (valist, nextarg); - else - std_expand_builtin_va_start (valist, nextarg); - return const0_rtx; -} + +/* Reconstitute a mode for a __sync intrinsic operation. Since the type of + the pointer in these functions is void*, the tree optimizers may remove + casts. The mode computed in expand_builtin isn't reliable either, due + to __sync_bool_compare_and_swap. -/* Expand EXP, a call to __builtin_va_end. */ + FCODE_DIFF should be fcode - base, where base is the FOO_1 code for the + group of builtins. This gives us log2 of the mode size. */ -static rtx -expand_builtin_va_end (tree exp) +static inline machine_mode +get_builtin_sync_mode (int fcode_diff) { - tree valist = CALL_EXPR_ARG (exp, 0); - - /* Evaluate for side effects, if needed. I hate macros that don't - do that. */ - if (TREE_SIDE_EFFECTS (valist)) - expand_expr (valist, const0_rtx, VOIDmode, EXPAND_NORMAL); - - return const0_rtx; + /* The size is not negotiable, so ask not to get BLKmode in return + if the target indicates that a smaller size would be better. */ + return int_mode_for_size (BITS_PER_UNIT << fcode_diff, 0).require (); } -/* Expand EXP, a call to __builtin_va_copy. We do this as a - builtin rather than just as an assignment in stdarg.h because of the - nastiness of array-type va_list types. */ +/* Expand the memory expression LOC and return the appropriate memory operand + for the builtin_sync operations. */ static rtx -expand_builtin_va_copy (tree exp) +get_builtin_sync_mem (tree loc, machine_mode mode) { - tree dst, src, t; - location_t loc = EXPR_LOCATION (exp); + rtx addr, mem; + int addr_space = TYPE_ADDR_SPACE (POINTER_TYPE_P (TREE_TYPE (loc)) + ? TREE_TYPE (TREE_TYPE (loc)) + : TREE_TYPE (loc)); + scalar_int_mode addr_mode = targetm.addr_space.address_mode (addr_space); - dst = CALL_EXPR_ARG (exp, 0); - src = CALL_EXPR_ARG (exp, 1); + addr = expand_expr (loc, NULL_RTX, addr_mode, EXPAND_SUM); + addr = convert_memory_address (addr_mode, addr); - dst = stabilize_va_list_loc (loc, dst, 1); - src = stabilize_va_list_loc (loc, src, 0); + /* Note that we explicitly do not want any alias information for this + memory, so that we kill all other live memories. Otherwise we don't + satisfy the full barrier semantics of the intrinsic. */ + mem = gen_rtx_MEM (mode, addr); - gcc_assert (cfun != NULL && cfun->decl != NULL_TREE); + set_mem_addr_space (mem, addr_space); - if (TREE_CODE (targetm.fn_abi_va_list (cfun->decl)) != ARRAY_TYPE) - { - t = build2 (MODIFY_EXPR, targetm.fn_abi_va_list (cfun->decl), dst, src); - TREE_SIDE_EFFECTS (t) = 1; - expand_expr (t, const0_rtx, VOIDmode, EXPAND_NORMAL); - } - else - { - rtx dstb, srcb, size; + mem = validize_mem (mem); - /* Evaluate to pointers. */ - dstb = expand_expr (dst, NULL_RTX, Pmode, EXPAND_NORMAL); - srcb = expand_expr (src, NULL_RTX, Pmode, EXPAND_NORMAL); - size = expand_expr (TYPE_SIZE_UNIT (targetm.fn_abi_va_list (cfun->decl)), - NULL_RTX, VOIDmode, EXPAND_NORMAL); + /* The alignment needs to be at least according to that of the mode. */ + set_mem_align (mem, MAX (GET_MODE_ALIGNMENT (mode), + get_pointer_alignment (loc))); + set_mem_alias_set (mem, ALIAS_SET_MEMORY_BARRIER); + MEM_VOLATILE_P (mem) = 1; - dstb = convert_memory_address (Pmode, dstb); - srcb = convert_memory_address (Pmode, srcb); + return mem; +} - /* "Dereference" to BLKmode memories. */ - dstb = gen_rtx_MEM (BLKmode, dstb); - set_mem_alias_set (dstb, get_alias_set (TREE_TYPE (TREE_TYPE (dst)))); - set_mem_align (dstb, TYPE_ALIGN (targetm.fn_abi_va_list (cfun->decl))); - srcb = gen_rtx_MEM (BLKmode, srcb); - set_mem_alias_set (srcb, get_alias_set (TREE_TYPE (TREE_TYPE (src)))); - set_mem_align (srcb, TYPE_ALIGN (targetm.fn_abi_va_list (cfun->decl))); +/* Make sure an argument is in the right mode. + EXP is the tree argument. + MODE is the mode it should be in. */ - /* Copy. */ - emit_block_move (dstb, srcb, size, BLOCK_OP_NORMAL); +static rtx +expand_expr_force_mode (tree exp, machine_mode mode) +{ + rtx val; + machine_mode old_mode; + + if (TREE_CODE (exp) == SSA_NAME + && TYPE_MODE (TREE_TYPE (exp)) != mode) + { + /* Undo argument promotion if possible, as combine might not + be able to do it later due to MEM_VOLATILE_P uses in the + patterns. */ + gimple *g = get_gimple_for_ssa_name (exp); + if (g && gimple_assign_cast_p (g)) + { + tree rhs = gimple_assign_rhs1 (g); + tree_code code = gimple_assign_rhs_code (g); + if (CONVERT_EXPR_CODE_P (code) + && TYPE_MODE (TREE_TYPE (rhs)) == mode + && INTEGRAL_TYPE_P (TREE_TYPE (exp)) + && INTEGRAL_TYPE_P (TREE_TYPE (rhs)) + && (TYPE_PRECISION (TREE_TYPE (exp)) + > TYPE_PRECISION (TREE_TYPE (rhs)))) + exp = rhs; + } } - return const0_rtx; + val = expand_expr (exp, NULL_RTX, mode, EXPAND_NORMAL); + /* If VAL is promoted to a wider mode, convert it back to MODE. Take care + of CONST_INTs, where we know the old_mode only from the call argument. */ + + old_mode = GET_MODE (val); + if (old_mode == VOIDmode) + old_mode = TYPE_MODE (TREE_TYPE (exp)); + val = convert_modes (mode, old_mode, val, 1); + return val; } -/* Expand a call to one of the builtin functions __builtin_frame_address or - __builtin_return_address. */ + +/* Expand the __sync_xxx_and_fetch and __sync_fetch_and_xxx intrinsics. + EXP is the CALL_EXPR. CODE is the rtx code + that corresponds to the arithmetic or logical operation from the name; + an exception here is that NOT actually means NAND. TARGET is an optional + place for us to store the results; AFTER is true if this is the + fetch_and_xxx form. */ static rtx -expand_builtin_frame_address (tree fndecl, tree exp) +expand_builtin_sync_operation (machine_mode mode, tree exp, + enum rtx_code code, bool after, + rtx target) { - /* The argument must be a nonnegative integer constant. - It counts the number of frames to scan up the stack. - The value is either the frame pointer value or the return - address saved in that frame. */ - if (call_expr_nargs (exp) == 0) - /* Warning about missing arg was already issued. */ - return const0_rtx; - else if (! tree_fits_uhwi_p (CALL_EXPR_ARG (exp, 0))) - { - error ("invalid argument to %qD", fndecl); - return const0_rtx; - } - else + rtx val, mem; + location_t loc = EXPR_LOCATION (exp); + + if (code == NOT && warn_sync_nand) { - /* Number of frames to scan up the stack. */ - unsigned HOST_WIDE_INT count = tree_to_uhwi (CALL_EXPR_ARG (exp, 0)); + tree fndecl = get_callee_fndecl (exp); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - rtx tem = expand_builtin_return_addr (DECL_FUNCTION_CODE (fndecl), count); + static bool warned_f_a_n, warned_n_a_f; - /* Some ports cannot access arbitrary stack frames. */ - if (tem == NULL) + switch (fcode) { - warning (0, "unsupported argument to %qD", fndecl); - return const0_rtx; - } + case BUILT_IN_SYNC_FETCH_AND_NAND_1: + case BUILT_IN_SYNC_FETCH_AND_NAND_2: + case BUILT_IN_SYNC_FETCH_AND_NAND_4: + case BUILT_IN_SYNC_FETCH_AND_NAND_8: + case BUILT_IN_SYNC_FETCH_AND_NAND_16: + if (warned_f_a_n) + break; - if (count) - { - /* Warn since no effort is made to ensure that any frame - beyond the current one exists or can be safely reached. */ - warning (OPT_Wframe_address, "calling %qD with " - "a nonzero argument is unsafe", fndecl); - } + fndecl = builtin_decl_implicit (BUILT_IN_SYNC_FETCH_AND_NAND_N); + inform (loc, "%qD changed semantics in GCC 4.4", fndecl); + warned_f_a_n = true; + break; - /* For __builtin_frame_address, return what we've got. */ - if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_FRAME_ADDRESS) - return tem; + case BUILT_IN_SYNC_NAND_AND_FETCH_1: + case BUILT_IN_SYNC_NAND_AND_FETCH_2: + case BUILT_IN_SYNC_NAND_AND_FETCH_4: + case BUILT_IN_SYNC_NAND_AND_FETCH_8: + case BUILT_IN_SYNC_NAND_AND_FETCH_16: + if (warned_n_a_f) + break; - if (!REG_P (tem) - && ! CONSTANT_P (tem)) - tem = copy_addr_to_reg (tem); - return tem; + fndecl = builtin_decl_implicit (BUILT_IN_SYNC_NAND_AND_FETCH_N); + inform (loc, "%qD changed semantics in GCC 4.4", fndecl); + warned_n_a_f = true; + break; + + default: + gcc_unreachable (); + } } + + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + + return expand_atomic_fetch_op (target, mem, val, code, MEMMODEL_SYNC_SEQ_CST, + after); } -/* Expand EXP, a call to the alloca builtin. Return NULL_RTX if we - failed and the caller should emit a normal call. */ +/* Expand the __sync_val_compare_and_swap and __sync_bool_compare_and_swap + intrinsics. EXP is the CALL_EXPR. IS_BOOL is + true if this is the boolean form. TARGET is a place for us to store the + results; this is NOT optional if IS_BOOL is true. */ static rtx -expand_builtin_alloca (tree exp) +expand_builtin_compare_and_swap (machine_mode mode, tree exp, + bool is_bool, rtx target) { - rtx op0; - rtx result; - unsigned int align; - tree fndecl = get_callee_fndecl (exp); - HOST_WIDE_INT max_size; - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - bool alloca_for_var = CALL_ALLOCA_FOR_VAR_P (exp); - bool valid_arglist - = (fcode == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX - ? validate_arglist (exp, INTEGER_TYPE, INTEGER_TYPE, INTEGER_TYPE, - VOID_TYPE) - : fcode == BUILT_IN_ALLOCA_WITH_ALIGN - ? validate_arglist (exp, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE) - : validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)); + rtx old_val, new_val, mem; + rtx *pbool, *poval; - if (!valid_arglist) - return NULL_RTX; + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + old_val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + new_val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 2), mode); - if ((alloca_for_var - && warn_vla_limit >= HOST_WIDE_INT_MAX - && warn_alloc_size_limit < warn_vla_limit) - || (!alloca_for_var - && warn_alloca_limit >= HOST_WIDE_INT_MAX - && warn_alloc_size_limit < warn_alloca_limit - )) + pbool = poval = NULL; + if (target != const0_rtx) { - /* -Walloca-larger-than and -Wvla-larger-than settings of - less than HOST_WIDE_INT_MAX override the more general - -Walloc-size-larger-than so unless either of the former - options is smaller than the last one (wchich would imply - that the call was already checked), check the alloca - arguments for overflow. */ - tree args[] = { CALL_EXPR_ARG (exp, 0), NULL_TREE }; - int idx[] = { 0, -1 }; - maybe_warn_alloc_args_overflow (fndecl, exp, args, idx); + if (is_bool) + pbool = ⌖ + else + poval = ⌖ } + if (!expand_atomic_compare_and_swap (pbool, poval, mem, old_val, new_val, + false, MEMMODEL_SYNC_SEQ_CST, + MEMMODEL_SYNC_SEQ_CST)) + return NULL_RTX; - /* Compute the argument. */ - op0 = expand_normal (CALL_EXPR_ARG (exp, 0)); - - /* Compute the alignment. */ - align = (fcode == BUILT_IN_ALLOCA - ? BIGGEST_ALIGNMENT - : TREE_INT_CST_LOW (CALL_EXPR_ARG (exp, 1))); - - /* Compute the maximum size. */ - max_size = (fcode == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX - ? TREE_INT_CST_LOW (CALL_EXPR_ARG (exp, 2)) - : -1); - - /* Allocate the desired space. If the allocation stems from the declaration - of a variable-sized object, it cannot accumulate. */ - result - = allocate_dynamic_stack_space (op0, 0, align, max_size, alloca_for_var); - result = convert_memory_address (ptr_mode, result); - - /* Dynamic allocations for variables are recorded during gimplification. */ - if (!alloca_for_var && (flag_callgraph_info & CALLGRAPH_INFO_DYNAMIC_ALLOC)) - record_dynamic_alloc (exp); - - return result; + return target; } -/* Emit a call to __asan_allocas_unpoison call in EXP. Add to second argument - of the call virtual_stack_dynamic_rtx - stack_pointer_rtx, which is the - STACK_DYNAMIC_OFFSET value. See motivation for this in comment to - handle_builtin_stack_restore function. */ +/* Expand the __sync_lock_test_and_set intrinsic. Note that the most + general form is actually an atomic exchange, and some targets only + support a reduced form with the second argument being a constant 1. + EXP is the CALL_EXPR; TARGET is an optional place for us to store + the results. */ static rtx -expand_asan_emit_allocas_unpoison (tree exp) +expand_builtin_sync_lock_test_and_set (machine_mode mode, tree exp, + rtx target) { - tree arg0 = CALL_EXPR_ARG (exp, 0); - tree arg1 = CALL_EXPR_ARG (exp, 1); - rtx top = expand_expr (arg0, NULL_RTX, ptr_mode, EXPAND_NORMAL); - rtx bot = expand_expr (arg1, NULL_RTX, ptr_mode, EXPAND_NORMAL); - rtx off = expand_simple_binop (Pmode, MINUS, virtual_stack_dynamic_rtx, - stack_pointer_rtx, NULL_RTX, 0, - OPTAB_LIB_WIDEN); - off = convert_modes (ptr_mode, Pmode, off, 0); - bot = expand_simple_binop (ptr_mode, PLUS, bot, off, NULL_RTX, 0, - OPTAB_LIB_WIDEN); - rtx ret = init_one_libfunc ("__asan_allocas_unpoison"); - ret = emit_library_call_value (ret, NULL_RTX, LCT_NORMAL, ptr_mode, - top, ptr_mode, bot, ptr_mode); - return ret; + rtx val, mem; + + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + + return expand_sync_lock_test_and_set (target, mem, val); } -/* Expand a call to bswap builtin in EXP. - Return NULL_RTX if a normal call should be emitted rather than expanding the - function in-line. If convenient, the result should be placed in TARGET. - SUBTARGET may be used as the target for computing one of EXP's operands. */ +/* Expand the __sync_lock_release intrinsic. EXP is the CALL_EXPR. */ -static rtx -expand_builtin_bswap (machine_mode target_mode, tree exp, rtx target, - rtx subtarget) +static void +expand_builtin_sync_lock_release (machine_mode mode, tree exp) { - tree arg; - rtx op0; + rtx mem; - if (!validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - arg = CALL_EXPR_ARG (exp, 0); - op0 = expand_expr (arg, - subtarget && GET_MODE (subtarget) == target_mode - ? subtarget : NULL_RTX, - target_mode, EXPAND_NORMAL); - if (GET_MODE (op0) != target_mode) - op0 = convert_to_mode (target_mode, op0, 1); - - target = expand_unop (target_mode, bswap_optab, op0, target, 1); - - gcc_assert (target); - - return convert_to_mode (target_mode, target, 1); + expand_atomic_store (mem, const0_rtx, MEMMODEL_SYNC_RELEASE, true); } -/* Expand a call to a unary builtin in EXP. - Return NULL_RTX if a normal call should be emitted rather than expanding the - function in-line. If convenient, the result should be placed in TARGET. - SUBTARGET may be used as the target for computing one of EXP's operands. */ +/* Given an integer representing an ``enum memmodel'', verify its + correctness and return the memory model enum. */ -static rtx -expand_builtin_unop (machine_mode target_mode, tree exp, rtx target, - rtx subtarget, optab op_optab) +static enum memmodel +get_memmodel (tree exp) { - rtx op0; - - if (!validate_arglist (exp, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; + rtx op; + unsigned HOST_WIDE_INT val; + location_t loc + = expansion_point_location_if_in_system_header (input_location); - /* Compute the argument. */ - op0 = expand_expr (CALL_EXPR_ARG (exp, 0), - (subtarget - && (TYPE_MODE (TREE_TYPE (CALL_EXPR_ARG (exp, 0))) - == GET_MODE (subtarget))) ? subtarget : NULL_RTX, - VOIDmode, EXPAND_NORMAL); - /* Compute op, into TARGET if possible. - Set TARGET to wherever the result comes back. */ - target = expand_unop (TYPE_MODE (TREE_TYPE (CALL_EXPR_ARG (exp, 0))), - op_optab, op0, target, op_optab != clrsb_optab); - gcc_assert (target); + /* If the parameter is not a constant, it's a run time value so we'll just + convert it to MEMMODEL_SEQ_CST to avoid annoying runtime checking. */ + if (TREE_CODE (exp) != INTEGER_CST) + return MEMMODEL_SEQ_CST; - return convert_to_mode (target_mode, target, 0); -} + op = expand_normal (exp); -/* Expand a call to __builtin_expect. We just return our argument - as the builtin_expect semantic should've been already executed by - tree branch prediction pass. */ + val = INTVAL (op); + if (targetm.memmodel_check) + val = targetm.memmodel_check (val); + else if (val & ~MEMMODEL_MASK) + { + warning_at (loc, OPT_Winvalid_memory_model, + "unknown architecture specifier in memory model to builtin"); + return MEMMODEL_SEQ_CST; + } -static rtx -expand_builtin_expect (tree exp, rtx target) -{ - tree arg; + /* Should never see a user explicit SYNC memodel model, so >= LAST works. */ + if (memmodel_base (val) >= MEMMODEL_LAST) + { + warning_at (loc, OPT_Winvalid_memory_model, + "invalid memory model argument to builtin"); + return MEMMODEL_SEQ_CST; + } - if (call_expr_nargs (exp) < 2) - return const0_rtx; - arg = CALL_EXPR_ARG (exp, 0); + /* Workaround for Bugzilla 59448. GCC doesn't track consume properly, so + be conservative and promote consume to acquire. */ + if (val == MEMMODEL_CONSUME) + val = MEMMODEL_ACQUIRE; - target = expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); - /* When guessing was done, the hints should be already stripped away. */ - gcc_assert (!flag_guess_branch_prob - || optimize == 0 || seen_error ()); - return target; + return (enum memmodel) val; } -/* Expand a call to __builtin_expect_with_probability. We just return our - argument as the builtin_expect semantic should've been already executed by - tree branch prediction pass. */ +/* Expand the __atomic_exchange intrinsic: + TYPE __atomic_exchange (TYPE *object, TYPE desired, enum memmodel) + EXP is the CALL_EXPR. + TARGET is an optional place for us to store the results. */ static rtx -expand_builtin_expect_with_probability (tree exp, rtx target) +expand_builtin_atomic_exchange (machine_mode mode, tree exp, rtx target) { - tree arg; + rtx val, mem; + enum memmodel model; - if (call_expr_nargs (exp) < 3) - return const0_rtx; - arg = CALL_EXPR_ARG (exp, 0); + model = get_memmodel (CALL_EXPR_ARG (exp, 2)); - target = expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); - /* When guessing was done, the hints should be already stripped away. */ - gcc_assert (!flag_guess_branch_prob - || optimize == 0 || seen_error ()); - return target; -} + if (!flag_inline_atomics) + return NULL_RTX; + + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + return expand_atomic_exchange (target, mem, val, model); +} -/* Expand a call to __builtin_assume_aligned. We just return our first - argument as the builtin_assume_aligned semantic should've been already - executed by CCP. */ +/* Expand the __atomic_compare_exchange intrinsic: + bool __atomic_compare_exchange (TYPE *object, TYPE *expect, + TYPE desired, BOOL weak, + enum memmodel success, + enum memmodel failure) + EXP is the CALL_EXPR. + TARGET is an optional place for us to store the results. */ static rtx -expand_builtin_assume_aligned (tree exp, rtx target) +expand_builtin_atomic_compare_exchange (machine_mode mode, tree exp, + rtx target) { - if (call_expr_nargs (exp) < 2) - return const0_rtx; - target = expand_expr (CALL_EXPR_ARG (exp, 0), target, VOIDmode, - EXPAND_NORMAL); - gcc_assert (!TREE_SIDE_EFFECTS (CALL_EXPR_ARG (exp, 1)) - && (call_expr_nargs (exp) < 3 - || !TREE_SIDE_EFFECTS (CALL_EXPR_ARG (exp, 2)))); - return target; -} + rtx expect, desired, mem, oldval; + rtx_code_label *label; + enum memmodel success, failure; + tree weak; + bool is_weak; + location_t loc + = expansion_point_location_if_in_system_header (input_location); -void -expand_builtin_trap (void) -{ - if (targetm.have_trap ()) + success = get_memmodel (CALL_EXPR_ARG (exp, 4)); + failure = get_memmodel (CALL_EXPR_ARG (exp, 5)); + + if (failure > success) { - rtx_insn *insn = emit_insn (targetm.gen_trap ()); - /* For trap insns when not accumulating outgoing args force - REG_ARGS_SIZE note to prevent crossjumping of calls with - different args sizes. */ - if (!ACCUMULATE_OUTGOING_ARGS) - add_args_size_note (insn, stack_pointer_delta); + warning_at (loc, OPT_Winvalid_memory_model, + "failure memory model cannot be stronger than success " + "memory model for %<__atomic_compare_exchange%>"); + success = MEMMODEL_SEQ_CST; } - else + + if (is_mm_release (failure) || is_mm_acq_rel (failure)) { - tree fn = builtin_decl_implicit (BUILT_IN_ABORT); - tree call_expr = build_call_expr (fn, 0); - expand_call (call_expr, NULL_RTX, false); + warning_at (loc, OPT_Winvalid_memory_model, + "invalid failure memory model for " + "%<__atomic_compare_exchange%>"); + failure = MEMMODEL_SEQ_CST; + success = MEMMODEL_SEQ_CST; } - emit_barrier (); -} + + if (!flag_inline_atomics) + return NULL_RTX; -/* Expand a call to __builtin_unreachable. We do nothing except emit - a barrier saying that control flow will not pass here. + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - It is the responsibility of the program being compiled to ensure - that control flow does never reach __builtin_unreachable. */ -static void -expand_builtin_unreachable (void) -{ - emit_barrier (); -} + expect = expand_normal (CALL_EXPR_ARG (exp, 1)); + expect = convert_memory_address (Pmode, expect); + expect = gen_rtx_MEM (mode, expect); + desired = expand_expr_force_mode (CALL_EXPR_ARG (exp, 2), mode); -/* Expand EXP, a call to fabs, fabsf or fabsl. - Return NULL_RTX if a normal call should be emitted rather than expanding - the function inline. If convenient, the result should be placed - in TARGET. SUBTARGET may be used as the target for computing - the operand. */ + weak = CALL_EXPR_ARG (exp, 3); + is_weak = false; + if (tree_fits_shwi_p (weak) && tree_to_shwi (weak) != 0) + is_weak = true; -static rtx -expand_builtin_fabs (tree exp, rtx target, rtx subtarget) -{ - machine_mode mode; - tree arg; - rtx op0; + if (target == const0_rtx) + target = NULL; - if (!validate_arglist (exp, REAL_TYPE, VOID_TYPE)) + /* Lest the rtl backend create a race condition with an imporoper store + to memory, always create a new pseudo for OLDVAL. */ + oldval = NULL; + + if (!expand_atomic_compare_and_swap (&target, &oldval, mem, expect, desired, + is_weak, success, failure)) return NULL_RTX; - arg = CALL_EXPR_ARG (exp, 0); - CALL_EXPR_ARG (exp, 0) = arg = builtin_save_expr (arg); - mode = TYPE_MODE (TREE_TYPE (arg)); - op0 = expand_expr (arg, subtarget, VOIDmode, EXPAND_NORMAL); - return expand_abs (mode, op0, target, 0, safe_from_p (target, arg, 1)); + /* Conditionally store back to EXPECT, lest we create a race condition + with an improper store to memory. */ + /* ??? With a rearrangement of atomics at the gimple level, we can handle + the normal case where EXPECT is totally private, i.e. a register. At + which point the store can be unconditional. */ + label = gen_label_rtx (); + emit_cmp_and_jump_insns (target, const0_rtx, NE, NULL, + GET_MODE (target), 1, label); + emit_move_insn (expect, oldval); + emit_label (label); + + return target; } -/* Expand EXP, a call to copysign, copysignf, or copysignl. - Return NULL is a normal call should be emitted rather than expanding the - function inline. If convenient, the result should be placed in TARGET. - SUBTARGET may be used as the target for computing the operand. */ +/* Helper function for expand_ifn_atomic_compare_exchange - expand + internal ATOMIC_COMPARE_EXCHANGE call into __atomic_compare_exchange_N + call. The weak parameter must be dropped to match the expected parameter + list and the expected argument changed from value to pointer to memory + slot. */ -static rtx -expand_builtin_copysign (tree exp, rtx target, rtx subtarget) +static void +expand_ifn_atomic_compare_exchange_into_call (gcall *call, machine_mode mode) { - rtx op0, op1; - tree arg; - - if (!validate_arglist (exp, REAL_TYPE, REAL_TYPE, VOID_TYPE)) - return NULL_RTX; + unsigned int z; + vec *vec; - arg = CALL_EXPR_ARG (exp, 0); - op0 = expand_expr (arg, subtarget, VOIDmode, EXPAND_NORMAL); - - arg = CALL_EXPR_ARG (exp, 1); - op1 = expand_normal (arg); - - return expand_copysign (op0, op1, target); + vec_alloc (vec, 5); + vec->quick_push (gimple_call_arg (call, 0)); + tree expected = gimple_call_arg (call, 1); + rtx x = assign_stack_temp_for_type (mode, GET_MODE_SIZE (mode), + TREE_TYPE (expected)); + rtx expd = expand_expr (expected, x, mode, EXPAND_NORMAL); + if (expd != x) + emit_move_insn (x, expd); + tree v = make_tree (TREE_TYPE (expected), x); + vec->quick_push (build1 (ADDR_EXPR, + build_pointer_type (TREE_TYPE (expected)), v)); + vec->quick_push (gimple_call_arg (call, 2)); + /* Skip the boolean weak parameter. */ + for (z = 4; z < 6; z++) + vec->quick_push (gimple_call_arg (call, z)); + /* At present we only have BUILT_IN_ATOMIC_COMPARE_EXCHANGE_{1,2,4,8,16}. */ + unsigned int bytes_log2 = exact_log2 (GET_MODE_SIZE (mode).to_constant ()); + gcc_assert (bytes_log2 < 5); + built_in_function fncode + = (built_in_function) ((int) BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1 + + bytes_log2); + tree fndecl = builtin_decl_explicit (fncode); + tree fn = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (fndecl)), + fndecl); + tree exp = build_call_vec (boolean_type_node, fn, vec); + tree lhs = gimple_call_lhs (call); + rtx boolret = expand_call (exp, NULL_RTX, lhs == NULL_TREE); + if (lhs) + { + rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); + if (GET_MODE (boolret) != mode) + boolret = convert_modes (mode, GET_MODE (boolret), boolret, 1); + x = force_reg (mode, x); + write_complex_part (target, boolret, true); + write_complex_part (target, x, false); + } } -/* Emit a call to __builtin___clear_cache. */ +/* Expand IFN_ATOMIC_COMPARE_EXCHANGE internal function. */ void -default_emit_call_builtin___clear_cache (rtx begin, rtx end) +expand_ifn_atomic_compare_exchange (gcall *call) { - rtx callee = gen_rtx_SYMBOL_REF (Pmode, - BUILTIN_ASM_NAME_PTR - (BUILT_IN_CLEAR_CACHE)); + int size = tree_to_shwi (gimple_call_arg (call, 3)) & 255; + gcc_assert (size == 1 || size == 2 || size == 4 || size == 8 || size == 16); + machine_mode mode = int_mode_for_size (BITS_PER_UNIT * size, 0).require (); + rtx expect, desired, mem, oldval, boolret; + enum memmodel success, failure; + tree lhs; + bool is_weak; + location_t loc + = expansion_point_location_if_in_system_header (gimple_location (call)); - emit_library_call (callee, - LCT_NORMAL, VOIDmode, - convert_memory_address (ptr_mode, begin), ptr_mode, - convert_memory_address (ptr_mode, end), ptr_mode); -} + success = get_memmodel (gimple_call_arg (call, 4)); + failure = get_memmodel (gimple_call_arg (call, 5)); -/* Emit a call to __builtin___clear_cache, unless the target specifies - it as do-nothing. This function can be used by trampoline - finalizers to duplicate the effects of expanding a call to the - clear_cache builtin. */ + if (failure > success) + { + warning_at (loc, OPT_Winvalid_memory_model, + "failure memory model cannot be stronger than success " + "memory model for %<__atomic_compare_exchange%>"); + success = MEMMODEL_SEQ_CST; + } -void -maybe_emit_call_builtin___clear_cache (rtx begin, rtx end) -{ - if ((GET_MODE (begin) != ptr_mode && GET_MODE (begin) != Pmode) - || (GET_MODE (end) != ptr_mode && GET_MODE (end) != Pmode)) + if (is_mm_release (failure) || is_mm_acq_rel (failure)) { - error ("both arguments to %<__builtin___clear_cache%> must be pointers"); - return; + warning_at (loc, OPT_Winvalid_memory_model, + "invalid failure memory model for " + "%<__atomic_compare_exchange%>"); + failure = MEMMODEL_SEQ_CST; + success = MEMMODEL_SEQ_CST; } - if (targetm.have_clear_cache ()) + if (!flag_inline_atomics) { - /* We have a "clear_cache" insn, and it will handle everything. */ - class expand_operand ops[2]; + expand_ifn_atomic_compare_exchange_into_call (call, mode); + return; + } - create_address_operand (&ops[0], begin); - create_address_operand (&ops[1], end); + /* Expand the operands. */ + mem = get_builtin_sync_mem (gimple_call_arg (call, 0), mode); - if (maybe_expand_insn (targetm.code_for_clear_cache, 2, ops)) - return; - } - else + expect = expand_expr_force_mode (gimple_call_arg (call, 1), mode); + desired = expand_expr_force_mode (gimple_call_arg (call, 2), mode); + + is_weak = (tree_to_shwi (gimple_call_arg (call, 3)) & 256) != 0; + + boolret = NULL; + oldval = NULL; + + if (!expand_atomic_compare_and_swap (&boolret, &oldval, mem, expect, desired, + is_weak, success, failure)) { -#ifndef CLEAR_INSN_CACHE - /* There is no "clear_cache" insn, and __clear_cache() in libgcc - does nothing. There is no need to call it. Do nothing. */ + expand_ifn_atomic_compare_exchange_into_call (call, mode); return; -#endif /* CLEAR_INSN_CACHE */ } - targetm.calls.emit_call_builtin___clear_cache (begin, end); + lhs = gimple_call_lhs (call); + if (lhs) + { + rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); + if (GET_MODE (boolret) != mode) + boolret = convert_modes (mode, GET_MODE (boolret), boolret, 1); + write_complex_part (target, boolret, true); + write_complex_part (target, oldval, false); + } } -/* Expand a call to __builtin___clear_cache. */ +/* Expand the __atomic_load intrinsic: + TYPE __atomic_load (TYPE *object, enum memmodel) + EXP is the CALL_EXPR. + TARGET is an optional place for us to store the results. */ -static void -expand_builtin___clear_cache (tree exp) +static rtx +expand_builtin_atomic_load (machine_mode mode, tree exp, rtx target) { - tree begin, end; - rtx begin_rtx, end_rtx; + rtx mem; + enum memmodel model; - /* We must not expand to a library call. If we did, any - fallback library function in libgcc that might contain a call to - __builtin___clear_cache() would recurse infinitely. */ - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + model = get_memmodel (CALL_EXPR_ARG (exp, 1)); + if (is_mm_release (model) || is_mm_acq_rel (model)) { - error ("both arguments to %<__builtin___clear_cache%> must be pointers"); - return; + location_t loc + = expansion_point_location_if_in_system_header (input_location); + warning_at (loc, OPT_Winvalid_memory_model, + "invalid memory model for %<__atomic_load%>"); + model = MEMMODEL_SEQ_CST; } - begin = CALL_EXPR_ARG (exp, 0); - begin_rtx = expand_expr (begin, NULL_RTX, Pmode, EXPAND_NORMAL); + if (!flag_inline_atomics) + return NULL_RTX; - end = CALL_EXPR_ARG (exp, 1); - end_rtx = expand_expr (end, NULL_RTX, Pmode, EXPAND_NORMAL); + /* Expand the operand. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - maybe_emit_call_builtin___clear_cache (begin_rtx, end_rtx); + return expand_atomic_load (target, mem, model); } -/* Given a trampoline address, make sure it satisfies TRAMPOLINE_ALIGNMENT. */ + +/* Expand the __atomic_store intrinsic: + void __atomic_store (TYPE *object, TYPE desired, enum memmodel) + EXP is the CALL_EXPR. + TARGET is an optional place for us to store the results. */ static rtx -round_trampoline_addr (rtx tramp) +expand_builtin_atomic_store (machine_mode mode, tree exp) { - rtx temp, addend, mask; + rtx mem, val; + enum memmodel model; - /* If we don't need too much alignment, we'll have been guaranteed - proper alignment by get_trampoline_type. */ - if (TRAMPOLINE_ALIGNMENT <= STACK_BOUNDARY) - return tramp; + model = get_memmodel (CALL_EXPR_ARG (exp, 2)); + if (!(is_mm_relaxed (model) || is_mm_seq_cst (model) + || is_mm_release (model))) + { + location_t loc + = expansion_point_location_if_in_system_header (input_location); + warning_at (loc, OPT_Winvalid_memory_model, + "invalid memory model for %<__atomic_store%>"); + model = MEMMODEL_SEQ_CST; + } - /* Round address up to desired boundary. */ - temp = gen_reg_rtx (Pmode); - addend = gen_int_mode (TRAMPOLINE_ALIGNMENT / BITS_PER_UNIT - 1, Pmode); - mask = gen_int_mode (-TRAMPOLINE_ALIGNMENT / BITS_PER_UNIT, Pmode); + if (!flag_inline_atomics) + return NULL_RTX; - temp = expand_simple_binop (Pmode, PLUS, tramp, addend, - temp, 0, OPTAB_LIB_WIDEN); - tramp = expand_simple_binop (Pmode, AND, temp, mask, - temp, 0, OPTAB_LIB_WIDEN); + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); - return tramp; + return expand_atomic_store (mem, val, model, false); } +/* Expand the __atomic_fetch_XXX intrinsic: + TYPE __atomic_fetch_XXX (TYPE *object, TYPE val, enum memmodel) + EXP is the CALL_EXPR. + TARGET is an optional place for us to store the results. + CODE is the operation, PLUS, MINUS, ADD, XOR, or IOR. + FETCH_AFTER is true if returning the result of the operation. + FETCH_AFTER is false if returning the value before the operation. + IGNORE is true if the result is not used. + EXT_CALL is the correct builtin for an external call if this cannot be + resolved to an instruction sequence. */ + static rtx -expand_builtin_init_trampoline (tree exp, bool onstack) +expand_builtin_atomic_fetch_op (machine_mode mode, tree exp, rtx target, + enum rtx_code code, bool fetch_after, + bool ignore, enum built_in_function ext_call) { - tree t_tramp, t_func, t_chain; - rtx m_tramp, r_tramp, r_chain, tmp; + rtx val, mem, ret; + enum memmodel model; + tree fndecl; + tree addr; - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, - POINTER_TYPE, VOID_TYPE)) - return NULL_RTX; + model = get_memmodel (CALL_EXPR_ARG (exp, 2)); - t_tramp = CALL_EXPR_ARG (exp, 0); - t_func = CALL_EXPR_ARG (exp, 1); - t_chain = CALL_EXPR_ARG (exp, 2); + /* Expand the operands. */ + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); - r_tramp = expand_normal (t_tramp); - m_tramp = gen_rtx_MEM (BLKmode, r_tramp); - MEM_NOTRAP_P (m_tramp) = 1; + /* Only try generating instructions if inlining is turned on. */ + if (flag_inline_atomics) + { + ret = expand_atomic_fetch_op (target, mem, val, code, model, fetch_after); + if (ret) + return ret; + } - /* If ONSTACK, the TRAMP argument should be the address of a field - within the local function's FRAME decl. Either way, let's see if - we can fill in the MEM_ATTRs for this memory. */ - if (TREE_CODE (t_tramp) == ADDR_EXPR) - set_mem_attributes (m_tramp, TREE_OPERAND (t_tramp, 0), true); + /* Return if a different routine isn't needed for the library call. */ + if (ext_call == BUILT_IN_NONE) + return NULL_RTX; - /* Creator of a heap trampoline is responsible for making sure the - address is aligned to at least STACK_BOUNDARY. Normally malloc - will ensure this anyhow. */ - tmp = round_trampoline_addr (r_tramp); - if (tmp != r_tramp) - { - m_tramp = change_address (m_tramp, BLKmode, tmp); - set_mem_align (m_tramp, TRAMPOLINE_ALIGNMENT); - set_mem_size (m_tramp, TRAMPOLINE_SIZE); - } + /* Change the call to the specified function. */ + fndecl = get_callee_fndecl (exp); + addr = CALL_EXPR_FN (exp); + STRIP_NOPS (addr); - /* The FUNC argument should be the address of the nested function. - Extract the actual function decl to pass to the hook. */ - gcc_assert (TREE_CODE (t_func) == ADDR_EXPR); - t_func = TREE_OPERAND (t_func, 0); - gcc_assert (TREE_CODE (t_func) == FUNCTION_DECL); + gcc_assert (TREE_OPERAND (addr, 0) == fndecl); + TREE_OPERAND (addr, 0) = builtin_decl_explicit (ext_call); - r_chain = expand_normal (t_chain); + /* If we will emit code after the call, the call cannot be a tail call. + If it is emitted as a tail call, a barrier is emitted after it, and + then all trailing code is removed. */ + if (!ignore) + CALL_EXPR_TAILCALL (exp) = 0; - /* Generate insns to initialize the trampoline. */ - targetm.calls.trampoline_init (m_tramp, t_func, r_chain); + /* Expand the call here so we can emit trailing code. */ + ret = expand_call (exp, target, ignore); - if (onstack) - { - trampolines_created = 1; + /* Replace the original function just in case it matters. */ + TREE_OPERAND (addr, 0) = fndecl; - if (targetm.calls.custom_function_descriptors != 0) - warning_at (DECL_SOURCE_LOCATION (t_func), OPT_Wtrampolines, - "trampoline generated for nested function %qD", t_func); + /* Then issue the arithmetic correction to return the right result. */ + if (!ignore) + { + if (code == NOT) + { + ret = expand_simple_binop (mode, AND, ret, val, NULL_RTX, true, + OPTAB_LIB_WIDEN); + ret = expand_simple_unop (mode, NOT, ret, target, true); + } + else + ret = expand_simple_binop (mode, code, ret, val, target, true, + OPTAB_LIB_WIDEN); } - - return const0_rtx; + return ret; } -static rtx -expand_builtin_adjust_trampoline (tree exp) +/* Expand IFN_ATOMIC_BIT_TEST_AND_* internal function. */ + +void +expand_ifn_atomic_bit_test_and (gcall *call) { - rtx tramp; + tree ptr = gimple_call_arg (call, 0); + tree bit = gimple_call_arg (call, 1); + tree flag = gimple_call_arg (call, 2); + tree lhs = gimple_call_lhs (call); + enum memmodel model = MEMMODEL_SYNC_SEQ_CST; + machine_mode mode = TYPE_MODE (TREE_TYPE (flag)); + enum rtx_code code; + optab optab; + class expand_operand ops[5]; - if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - return NULL_RTX; + gcc_assert (flag_inline_atomics); - tramp = expand_normal (CALL_EXPR_ARG (exp, 0)); - tramp = round_trampoline_addr (tramp); - if (targetm.calls.trampoline_adjust_address) - tramp = targetm.calls.trampoline_adjust_address (tramp); + if (gimple_call_num_args (call) == 4) + model = get_memmodel (gimple_call_arg (call, 3)); - return tramp; -} + rtx mem = get_builtin_sync_mem (ptr, mode); + rtx val = expand_expr_force_mode (bit, mode); -/* Expand a call to the builtin descriptor initialization routine. - A descriptor is made up of a couple of pointers to the static - chain and the code entry in this order. */ + switch (gimple_call_internal_fn (call)) + { + case IFN_ATOMIC_BIT_TEST_AND_SET: + code = IOR; + optab = atomic_bit_test_and_set_optab; + break; + case IFN_ATOMIC_BIT_TEST_AND_COMPLEMENT: + code = XOR; + optab = atomic_bit_test_and_complement_optab; + break; + case IFN_ATOMIC_BIT_TEST_AND_RESET: + code = AND; + optab = atomic_bit_test_and_reset_optab; + break; + default: + gcc_unreachable (); + } -static rtx -expand_builtin_init_descriptor (tree exp) -{ - tree t_descr, t_func, t_chain; - rtx m_descr, r_descr, r_func, r_chain; + if (lhs == NULL_TREE) + { + val = expand_simple_binop (mode, ASHIFT, const1_rtx, + val, NULL_RTX, true, OPTAB_DIRECT); + if (code == AND) + val = expand_simple_unop (mode, NOT, val, NULL_RTX, true); + expand_atomic_fetch_op (const0_rtx, mem, val, code, model, false); + return; + } - if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, POINTER_TYPE, - VOID_TYPE)) - return NULL_RTX; + rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); + enum insn_code icode = direct_optab_handler (optab, mode); + gcc_assert (icode != CODE_FOR_nothing); + create_output_operand (&ops[0], target, mode); + create_fixed_operand (&ops[1], mem); + create_convert_operand_to (&ops[2], val, mode, true); + create_integer_operand (&ops[3], model); + create_integer_operand (&ops[4], integer_onep (flag)); + if (maybe_expand_insn (icode, 5, ops)) + return; - t_descr = CALL_EXPR_ARG (exp, 0); - t_func = CALL_EXPR_ARG (exp, 1); - t_chain = CALL_EXPR_ARG (exp, 2); + rtx bitval = val; + val = expand_simple_binop (mode, ASHIFT, const1_rtx, + val, NULL_RTX, true, OPTAB_DIRECT); + rtx maskval = val; + if (code == AND) + val = expand_simple_unop (mode, NOT, val, NULL_RTX, true); + rtx result = expand_atomic_fetch_op (gen_reg_rtx (mode), mem, val, + code, model, false); + if (integer_onep (flag)) + { + result = expand_simple_binop (mode, ASHIFTRT, result, bitval, + NULL_RTX, true, OPTAB_DIRECT); + result = expand_simple_binop (mode, AND, result, const1_rtx, target, + true, OPTAB_DIRECT); + } + else + result = expand_simple_binop (mode, AND, result, maskval, target, true, + OPTAB_DIRECT); + if (result != target) + emit_move_insn (target, result); +} - r_descr = expand_normal (t_descr); - m_descr = gen_rtx_MEM (BLKmode, r_descr); - MEM_NOTRAP_P (m_descr) = 1; - set_mem_align (m_descr, GET_MODE_ALIGNMENT (ptr_mode)); +/* Expand an atomic clear operation. + void _atomic_clear (BOOL *obj, enum memmodel) + EXP is the call expression. */ - r_func = expand_normal (t_func); - r_chain = expand_normal (t_chain); +static rtx +expand_builtin_atomic_clear (tree exp) +{ + machine_mode mode; + rtx mem, ret; + enum memmodel model; - /* Generate insns to initialize the descriptor. */ - emit_move_insn (adjust_address_nv (m_descr, ptr_mode, 0), r_chain); - emit_move_insn (adjust_address_nv (m_descr, ptr_mode, - POINTER_SIZE / BITS_PER_UNIT), r_func); + mode = int_mode_for_size (BOOL_TYPE_SIZE, 0).require (); + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + model = get_memmodel (CALL_EXPR_ARG (exp, 1)); + + if (is_mm_consume (model) || is_mm_acquire (model) || is_mm_acq_rel (model)) + { + location_t loc + = expansion_point_location_if_in_system_header (input_location); + warning_at (loc, OPT_Winvalid_memory_model, + "invalid memory model for %<__atomic_store%>"); + model = MEMMODEL_SEQ_CST; + } + /* Try issuing an __atomic_store, and allow fallback to __sync_lock_release. + Failing that, a store is issued by __atomic_store. The only way this can + fail is if the bool type is larger than a word size. Unlikely, but + handle it anyway for completeness. Assume a single threaded model since + there is no atomic support in this case, and no barriers are required. */ + ret = expand_atomic_store (mem, const0_rtx, model, true); + if (!ret) + emit_move_insn (mem, const0_rtx); return const0_rtx; } -/* Expand a call to the builtin descriptor adjustment routine. */ +/* Expand an atomic test_and_set operation. + bool _atomic_test_and_set (BOOL *obj, enum memmodel) + EXP is the call expression. */ static rtx -expand_builtin_adjust_descriptor (tree exp) +expand_builtin_atomic_test_and_set (tree exp, rtx target) { - rtx tramp; + rtx mem; + enum memmodel model; + machine_mode mode; - if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - return NULL_RTX; + mode = int_mode_for_size (BOOL_TYPE_SIZE, 0).require (); + mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + model = get_memmodel (CALL_EXPR_ARG (exp, 1)); - tramp = expand_normal (CALL_EXPR_ARG (exp, 0)); + return expand_atomic_test_and_set (target, mem, model); +} - /* Unalign the descriptor to allow runtime identification. */ - tramp = plus_constant (ptr_mode, tramp, - targetm.calls.custom_function_descriptors); - return force_operand (tramp, NULL_RTX); -} +/* Return true if (optional) argument ARG1 of size ARG0 is always lock free on + this architecture. If ARG1 is NULL, use typical alignment for size ARG0. */ -/* Expand the call EXP to the built-in signbit, signbitf or signbitl - function. The function first checks whether the back end provides - an insn to implement signbit for the respective mode. If not, it - checks whether the floating point format of the value is such that - the sign bit can be extracted. If that is not the case, error out. - EXP is the expression that is a call to the builtin function; if - convenient, the result should be placed in TARGET. */ -static rtx -expand_builtin_signbit (tree exp, rtx target) +static tree +fold_builtin_atomic_always_lock_free (tree arg0, tree arg1) { - const struct real_format *fmt; - scalar_float_mode fmode; - scalar_int_mode rmode, imode; - tree arg; - int word, bitpos; - enum insn_code icode; - rtx temp; - location_t loc = EXPR_LOCATION (exp); - - if (!validate_arglist (exp, REAL_TYPE, VOID_TYPE)) - return NULL_RTX; + int size; + machine_mode mode; + unsigned int mode_align, type_align; - arg = CALL_EXPR_ARG (exp, 0); - fmode = SCALAR_FLOAT_TYPE_MODE (TREE_TYPE (arg)); - rmode = SCALAR_INT_TYPE_MODE (TREE_TYPE (exp)); - fmt = REAL_MODE_FORMAT (fmode); + if (TREE_CODE (arg0) != INTEGER_CST) + return NULL_TREE; - arg = builtin_save_expr (arg); + /* We need a corresponding integer mode for the access to be lock-free. */ + size = INTVAL (expand_normal (arg0)) * BITS_PER_UNIT; + if (!int_mode_for_size (size, 0).exists (&mode)) + return boolean_false_node; - /* Expand the argument yielding a RTX expression. */ - temp = expand_normal (arg); + mode_align = GET_MODE_ALIGNMENT (mode); - /* Check if the back end provides an insn that handles signbit for the - argument's mode. */ - icode = optab_handler (signbit_optab, fmode); - if (icode != CODE_FOR_nothing) + if (TREE_CODE (arg1) == INTEGER_CST) { - rtx_insn *last = get_last_insn (); - target = gen_reg_rtx (TYPE_MODE (TREE_TYPE (exp))); - if (maybe_emit_unop_insn (icode, target, temp, UNKNOWN)) - return target; - delete_insns_since (last); - } - - /* For floating point formats without a sign bit, implement signbit - as "ARG < 0.0". */ - bitpos = fmt->signbit_ro; - if (bitpos < 0) - { - /* But we can't do this if the format supports signed zero. */ - gcc_assert (!fmt->has_signed_zero || !HONOR_SIGNED_ZEROS (fmode)); + unsigned HOST_WIDE_INT val = UINTVAL (expand_normal (arg1)); - arg = fold_build2_loc (loc, LT_EXPR, TREE_TYPE (exp), arg, - build_real (TREE_TYPE (arg), dconst0)); - return expand_expr (arg, target, VOIDmode, EXPAND_NORMAL); - } + /* Either this argument is null, or it's a fake pointer encoding + the alignment of the object. */ + val = least_bit_hwi (val); + val *= BITS_PER_UNIT; - if (GET_MODE_SIZE (fmode) <= UNITS_PER_WORD) - { - imode = int_mode_for_mode (fmode).require (); - temp = gen_lowpart (imode, temp); + if (val == 0 || mode_align < val) + type_align = mode_align; + else + type_align = val; } else { - imode = word_mode; - /* Handle targets with different FP word orders. */ - if (FLOAT_WORDS_BIG_ENDIAN) - word = (GET_MODE_BITSIZE (fmode) - bitpos) / BITS_PER_WORD; - else - word = bitpos / BITS_PER_WORD; - temp = operand_subword_force (temp, word, fmode); - bitpos = bitpos % BITS_PER_WORD; - } - - /* Force the intermediate word_mode (or narrower) result into a - register. This avoids attempting to create paradoxical SUBREGs - of floating point modes below. */ - temp = force_reg (imode, temp); + tree ttype = TREE_TYPE (arg1); - /* If the bitpos is within the "result mode" lowpart, the operation - can be implement with a single bitwise AND. Otherwise, we need - a right shift and an AND. */ + /* This function is usually invoked and folded immediately by the front + end before anything else has a chance to look at it. The pointer + parameter at this point is usually cast to a void *, so check for that + and look past the cast. */ + if (CONVERT_EXPR_P (arg1) + && POINTER_TYPE_P (ttype) + && VOID_TYPE_P (TREE_TYPE (ttype)) + && POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (arg1, 0)))) + arg1 = TREE_OPERAND (arg1, 0); - if (bitpos < GET_MODE_BITSIZE (rmode)) - { - wide_int mask = wi::set_bit_in_zero (bitpos, GET_MODE_PRECISION (rmode)); + ttype = TREE_TYPE (arg1); + gcc_assert (POINTER_TYPE_P (ttype)); - if (GET_MODE_SIZE (imode) > GET_MODE_SIZE (rmode)) - temp = gen_lowpart (rmode, temp); - temp = expand_binop (rmode, and_optab, temp, - immed_wide_int_const (mask, rmode), - NULL_RTX, 1, OPTAB_LIB_WIDEN); - } - else - { - /* Perform a logical right shift to place the signbit in the least - significant bit, then truncate the result to the desired mode - and mask just this bit. */ - temp = expand_shift (RSHIFT_EXPR, imode, temp, bitpos, NULL_RTX, 1); - temp = gen_lowpart (rmode, temp); - temp = expand_binop (rmode, and_optab, temp, const1_rtx, - NULL_RTX, 1, OPTAB_LIB_WIDEN); + /* Get the underlying type of the object. */ + ttype = TREE_TYPE (ttype); + type_align = TYPE_ALIGN (ttype); } - return temp; + /* If the object has smaller alignment, the lock free routines cannot + be used. */ + if (type_align < mode_align) + return boolean_false_node; + + /* Check if a compare_and_swap pattern exists for the mode which represents + the required size. The pattern is not allowed to fail, so the existence + of the pattern indicates support is present. Also require that an + atomic load exists for the required size. */ + if (can_compare_and_swap_p (mode, true) && can_atomic_load_p (mode)) + return boolean_true_node; + else + return boolean_false_node; } -/* Expand fork or exec calls. TARGET is the desired target of the - call. EXP is the call. FN is the - identificator of the actual function. IGNORE is nonzero if the - value is to be ignored. */ +/* Return true if the parameters to call EXP represent an object which will + always generate lock free instructions. The first argument represents the + size of the object, and the second parameter is a pointer to the object + itself. If NULL is passed for the object, then the result is based on + typical alignment for an object of the specified size. Otherwise return + false. */ static rtx -expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore) +expand_builtin_atomic_always_lock_free (tree exp) { - tree id, decl; - tree call; + tree size; + tree arg0 = CALL_EXPR_ARG (exp, 0); + tree arg1 = CALL_EXPR_ARG (exp, 1); - if (DECL_FUNCTION_CODE (fn) != BUILT_IN_FORK) + if (TREE_CODE (arg0) != INTEGER_CST) { - tree path = CALL_EXPR_ARG (exp, 0); - /* Detect unterminated path. */ - if (!check_read_access (exp, path)) - return NULL_RTX; - - /* Also detect unterminated first argument. */ - switch (DECL_FUNCTION_CODE (fn)) - { - case BUILT_IN_EXECL: - case BUILT_IN_EXECLE: - case BUILT_IN_EXECLP: - if (!check_read_access (exp, path)) - return NULL_RTX; - default: - break; - } + error ("non-constant argument 1 to %qs", "__atomic_always_lock_free"); + return const0_rtx; } + size = fold_builtin_atomic_always_lock_free (arg0, arg1); + if (size == boolean_true_node) + return const1_rtx; + return const0_rtx; +} - /* If we are not profiling, just call the function. */ - if (!profile_arc_flag) - return NULL_RTX; - - /* Otherwise call the wrapper. This should be equivalent for the rest of - compiler, so the code does not diverge, and the wrapper may run the - code necessary for keeping the profiling sane. */ - - switch (DECL_FUNCTION_CODE (fn)) - { - case BUILT_IN_FORK: - id = get_identifier ("__gcov_fork"); - break; - - case BUILT_IN_EXECL: - id = get_identifier ("__gcov_execl"); - break; - - case BUILT_IN_EXECV: - id = get_identifier ("__gcov_execv"); - break; +/* Return a one or zero if it can be determined that object ARG1 of size ARG + is lock free on this architecture. */ - case BUILT_IN_EXECLP: - id = get_identifier ("__gcov_execlp"); - break; +static tree +fold_builtin_atomic_is_lock_free (tree arg0, tree arg1) +{ + if (!flag_inline_atomics) + return NULL_TREE; + + /* If it isn't always lock free, don't generate a result. */ + if (fold_builtin_atomic_always_lock_free (arg0, arg1) == boolean_true_node) + return boolean_true_node; - case BUILT_IN_EXECLE: - id = get_identifier ("__gcov_execle"); - break; + return NULL_TREE; +} - case BUILT_IN_EXECVP: - id = get_identifier ("__gcov_execvp"); - break; +/* Return true if the parameters to call EXP represent an object which will + always generate lock free instructions. The first argument represents the + size of the object, and the second parameter is a pointer to the object + itself. If NULL is passed for the object, then the result is based on + typical alignment for an object of the specified size. Otherwise return + NULL*/ - case BUILT_IN_EXECVE: - id = get_identifier ("__gcov_execve"); - break; +static rtx +expand_builtin_atomic_is_lock_free (tree exp) +{ + tree size; + tree arg0 = CALL_EXPR_ARG (exp, 0); + tree arg1 = CALL_EXPR_ARG (exp, 1); - default: - gcc_unreachable (); + if (!INTEGRAL_TYPE_P (TREE_TYPE (arg0))) + { + error ("non-integer argument 1 to %qs", "__atomic_is_lock_free"); + return NULL_RTX; } - decl = build_decl (DECL_SOURCE_LOCATION (fn), - FUNCTION_DECL, id, TREE_TYPE (fn)); - DECL_EXTERNAL (decl) = 1; - TREE_PUBLIC (decl) = 1; - DECL_ARTIFICIAL (decl) = 1; - TREE_NOTHROW (decl) = 1; - DECL_VISIBILITY (decl) = VISIBILITY_DEFAULT; - DECL_VISIBILITY_SPECIFIED (decl) = 1; - call = rewrite_call_expr (EXPR_LOCATION (exp), exp, 0, decl, 0); - return expand_call (call, target, ignore); - } + if (!flag_inline_atomics) + return NULL_RTX; + /* If the value is known at compile time, return the RTX for it. */ + size = fold_builtin_atomic_is_lock_free (arg0, arg1); + if (size == boolean_true_node) + return const1_rtx; - -/* Reconstitute a mode for a __sync intrinsic operation. Since the type of - the pointer in these functions is void*, the tree optimizers may remove - casts. The mode computed in expand_builtin isn't reliable either, due - to __sync_bool_compare_and_swap. + return NULL_RTX; +} - FCODE_DIFF should be fcode - base, where base is the FOO_1 code for the - group of builtins. This gives us log2 of the mode size. */ +/* Expand the __atomic_thread_fence intrinsic: + void __atomic_thread_fence (enum memmodel) + EXP is the CALL_EXPR. */ -static inline machine_mode -get_builtin_sync_mode (int fcode_diff) +static void +expand_builtin_atomic_thread_fence (tree exp) { - /* The size is not negotiable, so ask not to get BLKmode in return - if the target indicates that a smaller size would be better. */ - return int_mode_for_size (BITS_PER_UNIT << fcode_diff, 0).require (); + enum memmodel model = get_memmodel (CALL_EXPR_ARG (exp, 0)); + expand_mem_thread_fence (model); } -/* Expand the memory expression LOC and return the appropriate memory operand - for the builtin_sync operations. */ +/* Expand the __atomic_signal_fence intrinsic: + void __atomic_signal_fence (enum memmodel) + EXP is the CALL_EXPR. */ -static rtx -get_builtin_sync_mem (tree loc, machine_mode mode) +static void +expand_builtin_atomic_signal_fence (tree exp) { - rtx addr, mem; - int addr_space = TYPE_ADDR_SPACE (POINTER_TYPE_P (TREE_TYPE (loc)) - ? TREE_TYPE (TREE_TYPE (loc)) - : TREE_TYPE (loc)); - scalar_int_mode addr_mode = targetm.addr_space.address_mode (addr_space); - - addr = expand_expr (loc, NULL_RTX, addr_mode, EXPAND_SUM); - addr = convert_memory_address (addr_mode, addr); - - /* Note that we explicitly do not want any alias information for this - memory, so that we kill all other live memories. Otherwise we don't - satisfy the full barrier semantics of the intrinsic. */ - mem = gen_rtx_MEM (mode, addr); - - set_mem_addr_space (mem, addr_space); - - mem = validize_mem (mem); + enum memmodel model = get_memmodel (CALL_EXPR_ARG (exp, 0)); + expand_mem_signal_fence (model); +} - /* The alignment needs to be at least according to that of the mode. */ - set_mem_align (mem, MAX (GET_MODE_ALIGNMENT (mode), - get_pointer_alignment (loc))); - set_mem_alias_set (mem, ALIAS_SET_MEMORY_BARRIER); - MEM_VOLATILE_P (mem) = 1; +/* Expand the __sync_synchronize intrinsic. */ - return mem; +static void +expand_builtin_sync_synchronize (void) +{ + expand_mem_thread_fence (MEMMODEL_SYNC_SEQ_CST); } -/* Make sure an argument is in the right mode. - EXP is the tree argument. - MODE is the mode it should be in. */ - static rtx -expand_expr_force_mode (tree exp, machine_mode mode) +expand_builtin_thread_pointer (tree exp, rtx target) { - rtx val; - machine_mode old_mode; - - if (TREE_CODE (exp) == SSA_NAME - && TYPE_MODE (TREE_TYPE (exp)) != mode) + enum insn_code icode; + if (!validate_arglist (exp, VOID_TYPE)) + return const0_rtx; + icode = direct_optab_handler (get_thread_pointer_optab, Pmode); + if (icode != CODE_FOR_nothing) { - /* Undo argument promotion if possible, as combine might not - be able to do it later due to MEM_VOLATILE_P uses in the - patterns. */ - gimple *g = get_gimple_for_ssa_name (exp); - if (g && gimple_assign_cast_p (g)) - { - tree rhs = gimple_assign_rhs1 (g); - tree_code code = gimple_assign_rhs_code (g); - if (CONVERT_EXPR_CODE_P (code) - && TYPE_MODE (TREE_TYPE (rhs)) == mode - && INTEGRAL_TYPE_P (TREE_TYPE (exp)) - && INTEGRAL_TYPE_P (TREE_TYPE (rhs)) - && (TYPE_PRECISION (TREE_TYPE (exp)) - > TYPE_PRECISION (TREE_TYPE (rhs)))) - exp = rhs; - } + class expand_operand op; + /* If the target is not sutitable then create a new target. */ + if (target == NULL_RTX + || !REG_P (target) + || GET_MODE (target) != Pmode) + target = gen_reg_rtx (Pmode); + create_output_operand (&op, target, Pmode); + expand_insn (icode, 1, &op); + return target; } - - val = expand_expr (exp, NULL_RTX, mode, EXPAND_NORMAL); - /* If VAL is promoted to a wider mode, convert it back to MODE. Take care - of CONST_INTs, where we know the old_mode only from the call argument. */ - - old_mode = GET_MODE (val); - if (old_mode == VOIDmode) - old_mode = TYPE_MODE (TREE_TYPE (exp)); - val = convert_modes (mode, old_mode, val, 1); - return val; + error ("%<__builtin_thread_pointer%> is not supported on this target"); + return const0_rtx; } - -/* Expand the __sync_xxx_and_fetch and __sync_fetch_and_xxx intrinsics. - EXP is the CALL_EXPR. CODE is the rtx code - that corresponds to the arithmetic or logical operation from the name; - an exception here is that NOT actually means NAND. TARGET is an optional - place for us to store the results; AFTER is true if this is the - fetch_and_xxx form. */ - -static rtx -expand_builtin_sync_operation (machine_mode mode, tree exp, - enum rtx_code code, bool after, - rtx target) +static void +expand_builtin_set_thread_pointer (tree exp) { - rtx val, mem; - location_t loc = EXPR_LOCATION (exp); - - if (code == NOT && warn_sync_nand) + enum insn_code icode; + if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + return; + icode = direct_optab_handler (set_thread_pointer_optab, Pmode); + if (icode != CODE_FOR_nothing) { - tree fndecl = get_callee_fndecl (exp); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + class expand_operand op; + rtx val = expand_expr (CALL_EXPR_ARG (exp, 0), NULL_RTX, + Pmode, EXPAND_NORMAL); + create_input_operand (&op, val, Pmode); + expand_insn (icode, 1, &op); + return; + } + error ("%<__builtin_set_thread_pointer%> is not supported on this target"); +} - static bool warned_f_a_n, warned_n_a_f; + +/* Emit code to restore the current value of stack. */ - switch (fcode) - { - case BUILT_IN_SYNC_FETCH_AND_NAND_1: - case BUILT_IN_SYNC_FETCH_AND_NAND_2: - case BUILT_IN_SYNC_FETCH_AND_NAND_4: - case BUILT_IN_SYNC_FETCH_AND_NAND_8: - case BUILT_IN_SYNC_FETCH_AND_NAND_16: - if (warned_f_a_n) - break; +static void +expand_stack_restore (tree var) +{ + rtx_insn *prev; + rtx sa = expand_normal (var); - fndecl = builtin_decl_implicit (BUILT_IN_SYNC_FETCH_AND_NAND_N); - inform (loc, "%qD changed semantics in GCC 4.4", fndecl); - warned_f_a_n = true; - break; + sa = convert_memory_address (Pmode, sa); - case BUILT_IN_SYNC_NAND_AND_FETCH_1: - case BUILT_IN_SYNC_NAND_AND_FETCH_2: - case BUILT_IN_SYNC_NAND_AND_FETCH_4: - case BUILT_IN_SYNC_NAND_AND_FETCH_8: - case BUILT_IN_SYNC_NAND_AND_FETCH_16: - if (warned_n_a_f) - break; + prev = get_last_insn (); + emit_stack_restore (SAVE_BLOCK, sa); - fndecl = builtin_decl_implicit (BUILT_IN_SYNC_NAND_AND_FETCH_N); - inform (loc, "%qD changed semantics in GCC 4.4", fndecl); - warned_n_a_f = true; - break; + record_new_stack_level (); - default: - gcc_unreachable (); - } - } + fixup_args_size_notes (prev, get_last_insn (), 0); +} - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); +/* Emit code to save the current value of stack. */ - return expand_atomic_fetch_op (target, mem, val, code, MEMMODEL_SYNC_SEQ_CST, - after); +static rtx +expand_stack_save (void) +{ + rtx ret = NULL_RTX; + + emit_stack_save (SAVE_BLOCK, &ret); + return ret; } -/* Expand the __sync_val_compare_and_swap and __sync_bool_compare_and_swap - intrinsics. EXP is the CALL_EXPR. IS_BOOL is - true if this is the boolean form. TARGET is a place for us to store the - results; this is NOT optional if IS_BOOL is true. */ +/* Emit code to get the openacc gang, worker or vector id or size. */ static rtx -expand_builtin_compare_and_swap (machine_mode mode, tree exp, - bool is_bool, rtx target) +expand_builtin_goacc_parlevel_id_size (tree exp, rtx target, int ignore) { - rtx old_val, new_val, mem; - rtx *pbool, *poval; + const char *name; + rtx fallback_retval; + rtx_insn *(*gen_fn) (rtx, rtx); + switch (DECL_FUNCTION_CODE (get_callee_fndecl (exp))) + { + case BUILT_IN_GOACC_PARLEVEL_ID: + name = "__builtin_goacc_parlevel_id"; + fallback_retval = const0_rtx; + gen_fn = targetm.gen_oacc_dim_pos; + break; + case BUILT_IN_GOACC_PARLEVEL_SIZE: + name = "__builtin_goacc_parlevel_size"; + fallback_retval = const1_rtx; + gen_fn = targetm.gen_oacc_dim_size; + break; + default: + gcc_unreachable (); + } - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - old_val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); - new_val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 2), mode); + if (oacc_get_fn_attrib (current_function_decl) == NULL_TREE) + { + error ("%qs only supported in OpenACC code", name); + return const0_rtx; + } - pbool = poval = NULL; - if (target != const0_rtx) + tree arg = CALL_EXPR_ARG (exp, 0); + if (TREE_CODE (arg) != INTEGER_CST) { - if (is_bool) - pbool = ⌖ - else - poval = ⌖ + error ("non-constant argument 0 to %qs", name); + return const0_rtx; } - if (!expand_atomic_compare_and_swap (pbool, poval, mem, old_val, new_val, - false, MEMMODEL_SYNC_SEQ_CST, - MEMMODEL_SYNC_SEQ_CST)) - return NULL_RTX; - return target; -} + int dim = TREE_INT_CST_LOW (arg); + switch (dim) + { + case GOMP_DIM_GANG: + case GOMP_DIM_WORKER: + case GOMP_DIM_VECTOR: + break; + default: + error ("illegal argument 0 to %qs", name); + return const0_rtx; + } -/* Expand the __sync_lock_test_and_set intrinsic. Note that the most - general form is actually an atomic exchange, and some targets only - support a reduced form with the second argument being a constant 1. - EXP is the CALL_EXPR; TARGET is an optional place for us to store - the results. */ + if (ignore) + return target; -static rtx -expand_builtin_sync_lock_test_and_set (machine_mode mode, tree exp, - rtx target) -{ - rtx val, mem; + if (target == NULL_RTX) + target = gen_reg_rtx (TYPE_MODE (TREE_TYPE (exp))); - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + if (!targetm.have_oacc_dim_size ()) + { + emit_move_insn (target, fallback_retval); + return target; + } - return expand_sync_lock_test_and_set (target, mem, val); -} - -/* Expand the __sync_lock_release intrinsic. EXP is the CALL_EXPR. */ + rtx reg = MEM_P (target) ? gen_reg_rtx (GET_MODE (target)) : target; + emit_insn (gen_fn (reg, GEN_INT (dim))); + if (reg != target) + emit_move_insn (target, reg); -static void -expand_builtin_sync_lock_release (machine_mode mode, tree exp) -{ - rtx mem; + return target; +} - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); +/* Expand a string compare operation using a sequence of char comparison + to get rid of the calling overhead, with result going to TARGET if + that's convenient. - expand_atomic_store (mem, const0_rtx, MEMMODEL_SYNC_RELEASE, true); -} + VAR_STR is the variable string source; + CONST_STR is the constant string source; + LENGTH is the number of chars to compare; + CONST_STR_N indicates which source string is the constant string; + IS_MEMCMP indicates whether it's a memcmp or strcmp. + + to: (assume const_str_n is 2, i.e., arg2 is a constant string) -/* Given an integer representing an ``enum memmodel'', verify its - correctness and return the memory model enum. */ + target = (int) (unsigned char) var_str[0] + - (int) (unsigned char) const_str[0]; + if (target != 0) + goto ne_label; + ... + target = (int) (unsigned char) var_str[length - 2] + - (int) (unsigned char) const_str[length - 2]; + if (target != 0) + goto ne_label; + target = (int) (unsigned char) var_str[length - 1] + - (int) (unsigned char) const_str[length - 1]; + ne_label: + */ -static enum memmodel -get_memmodel (tree exp) +static rtx +inline_string_cmp (rtx target, tree var_str, const char *const_str, + unsigned HOST_WIDE_INT length, + int const_str_n, machine_mode mode) { - rtx op; - unsigned HOST_WIDE_INT val; - location_t loc - = expansion_point_location_if_in_system_header (input_location); - - /* If the parameter is not a constant, it's a run time value so we'll just - convert it to MEMMODEL_SEQ_CST to avoid annoying runtime checking. */ - if (TREE_CODE (exp) != INTEGER_CST) - return MEMMODEL_SEQ_CST; + HOST_WIDE_INT offset = 0; + rtx var_rtx_array + = get_memory_rtx (var_str, build_int_cst (unsigned_type_node,length)); + rtx var_rtx = NULL_RTX; + rtx const_rtx = NULL_RTX; + rtx result = target ? target : gen_reg_rtx (mode); + rtx_code_label *ne_label = gen_label_rtx (); + tree unit_type_node = unsigned_char_type_node; + scalar_int_mode unit_mode + = as_a TYPE_MODE (unit_type_node); - op = expand_normal (exp); + start_sequence (); - val = INTVAL (op); - if (targetm.memmodel_check) - val = targetm.memmodel_check (val); - else if (val & ~MEMMODEL_MASK) + for (unsigned HOST_WIDE_INT i = 0; i < length; i++) { - warning_at (loc, OPT_Winvalid_memory_model, - "unknown architecture specifier in memory model to builtin"); - return MEMMODEL_SEQ_CST; - } + var_rtx + = adjust_address (var_rtx_array, TYPE_MODE (unit_type_node), offset); + const_rtx = c_readstr (const_str + offset, unit_mode); + rtx op0 = (const_str_n == 1) ? const_rtx : var_rtx; + rtx op1 = (const_str_n == 1) ? var_rtx : const_rtx; - /* Should never see a user explicit SYNC memodel model, so >= LAST works. */ - if (memmodel_base (val) >= MEMMODEL_LAST) - { - warning_at (loc, OPT_Winvalid_memory_model, - "invalid memory model argument to builtin"); - return MEMMODEL_SEQ_CST; + op0 = convert_modes (mode, unit_mode, op0, 1); + op1 = convert_modes (mode, unit_mode, op1, 1); + result = expand_simple_binop (mode, MINUS, op0, op1, + result, 1, OPTAB_WIDEN); + if (i < length - 1) + emit_cmp_and_jump_insns (result, CONST0_RTX (mode), NE, NULL_RTX, + mode, true, ne_label); + offset += GET_MODE_SIZE (unit_mode); } - /* Workaround for Bugzilla 59448. GCC doesn't track consume properly, so - be conservative and promote consume to acquire. */ - if (val == MEMMODEL_CONSUME) - val = MEMMODEL_ACQUIRE; + emit_label (ne_label); + rtx_insn *insns = get_insns (); + end_sequence (); + emit_insn (insns); - return (enum memmodel) val; + return result; } -/* Expand the __atomic_exchange intrinsic: - TYPE __atomic_exchange (TYPE *object, TYPE desired, enum memmodel) - EXP is the CALL_EXPR. - TARGET is an optional place for us to store the results. */ +/* Inline expansion of a call to str(n)cmp and memcmp, with result going + to TARGET if that's convenient. + If the call is not been inlined, return NULL_RTX. */ static rtx -expand_builtin_atomic_exchange (machine_mode mode, tree exp, rtx target) +inline_expand_builtin_bytecmp (tree exp, rtx target) { - rtx val, mem; - enum memmodel model; - - model = get_memmodel (CALL_EXPR_ARG (exp, 2)); + tree fndecl = get_callee_fndecl (exp); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + bool is_ncmp = (fcode == BUILT_IN_STRNCMP || fcode == BUILT_IN_MEMCMP); - if (!flag_inline_atomics) + /* Do NOT apply this inlining expansion when optimizing for size or + optimization level below 2. */ + if (optimize < 2 || optimize_insn_for_size_p ()) return NULL_RTX; - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + gcc_checking_assert (fcode == BUILT_IN_STRCMP + || fcode == BUILT_IN_STRNCMP + || fcode == BUILT_IN_MEMCMP); - return expand_atomic_exchange (target, mem, val, model); -} + /* On a target where the type of the call (int) has same or narrower presicion + than unsigned char, give up the inlining expansion. */ + if (TYPE_PRECISION (unsigned_char_type_node) + >= TYPE_PRECISION (TREE_TYPE (exp))) + return NULL_RTX; -/* Expand the __atomic_compare_exchange intrinsic: - bool __atomic_compare_exchange (TYPE *object, TYPE *expect, - TYPE desired, BOOL weak, - enum memmodel success, - enum memmodel failure) - EXP is the CALL_EXPR. - TARGET is an optional place for us to store the results. */ + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); + tree len3_tree = is_ncmp ? CALL_EXPR_ARG (exp, 2) : NULL_TREE; -static rtx -expand_builtin_atomic_compare_exchange (machine_mode mode, tree exp, - rtx target) -{ - rtx expect, desired, mem, oldval; - rtx_code_label *label; - enum memmodel success, failure; - tree weak; - bool is_weak; - location_t loc - = expansion_point_location_if_in_system_header (input_location); + unsigned HOST_WIDE_INT len1 = 0; + unsigned HOST_WIDE_INT len2 = 0; + unsigned HOST_WIDE_INT len3 = 0; - success = get_memmodel (CALL_EXPR_ARG (exp, 4)); - failure = get_memmodel (CALL_EXPR_ARG (exp, 5)); + /* Get the object representation of the initializers of ARG1 and ARG2 + as strings, provided they refer to constant objects, with their byte + sizes in LEN1 and LEN2, respectively. */ + const char *bytes1 = getbyterep (arg1, &len1); + const char *bytes2 = getbyterep (arg2, &len2); - if (failure > success) + /* Fail if neither argument refers to an initialized constant. */ + if (!bytes1 && !bytes2) + return NULL_RTX; + + if (is_ncmp) { - warning_at (loc, OPT_Winvalid_memory_model, - "failure memory model cannot be stronger than success " - "memory model for %<__atomic_compare_exchange%>"); - success = MEMMODEL_SEQ_CST; + /* Fail if the memcmp/strncmp bound is not a constant. */ + if (!tree_fits_uhwi_p (len3_tree)) + return NULL_RTX; + + len3 = tree_to_uhwi (len3_tree); + + if (fcode == BUILT_IN_MEMCMP) + { + /* Fail if the memcmp bound is greater than the size of either + of the two constant objects. */ + if ((bytes1 && len1 < len3) + || (bytes2 && len2 < len3)) + return NULL_RTX; + } } - - if (is_mm_release (failure) || is_mm_acq_rel (failure)) + + if (fcode != BUILT_IN_MEMCMP) { - warning_at (loc, OPT_Winvalid_memory_model, - "invalid failure memory model for " - "%<__atomic_compare_exchange%>"); - failure = MEMMODEL_SEQ_CST; - success = MEMMODEL_SEQ_CST; + /* For string functions (i.e., strcmp and strncmp) reduce LEN1 + and LEN2 to the length of the nul-terminated string stored + in each. */ + if (bytes1 != NULL) + len1 = strnlen (bytes1, len1) + 1; + if (bytes2 != NULL) + len2 = strnlen (bytes2, len2) + 1; } - - if (!flag_inline_atomics) - return NULL_RTX; + /* See inline_string_cmp. */ + int const_str_n; + if (!len1) + const_str_n = 2; + else if (!len2) + const_str_n = 1; + else if (len2 > len1) + const_str_n = 1; + else + const_str_n = 2; - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + /* For strncmp only, compute the new bound as the smallest of + the lengths of the two strings (plus 1) and the bound provided + to the function. */ + unsigned HOST_WIDE_INT bound = (const_str_n == 1) ? len1 : len2; + if (is_ncmp && len3 < bound) + bound = len3; - expect = expand_normal (CALL_EXPR_ARG (exp, 1)); - expect = convert_memory_address (Pmode, expect); - expect = gen_rtx_MEM (mode, expect); - desired = expand_expr_force_mode (CALL_EXPR_ARG (exp, 2), mode); + /* If the bound of the comparison is larger than the threshold, + do nothing. */ + if (bound > (unsigned HOST_WIDE_INT) param_builtin_string_cmp_inline_length) + return NULL_RTX; - weak = CALL_EXPR_ARG (exp, 3); - is_weak = false; - if (tree_fits_shwi_p (weak) && tree_to_shwi (weak) != 0) - is_weak = true; + machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); - if (target == const0_rtx) - target = NULL; + /* Now, start inline expansion the call. */ + return inline_string_cmp (target, (const_str_n == 1) ? arg2 : arg1, + (const_str_n == 1) ? bytes1 : bytes2, bound, + const_str_n, mode); +} - /* Lest the rtl backend create a race condition with an imporoper store - to memory, always create a new pseudo for OLDVAL. */ - oldval = NULL; - - if (!expand_atomic_compare_and_swap (&target, &oldval, mem, expect, desired, - is_weak, success, failure)) - return NULL_RTX; - - /* Conditionally store back to EXPECT, lest we create a race condition - with an improper store to memory. */ - /* ??? With a rearrangement of atomics at the gimple level, we can handle - the normal case where EXPECT is totally private, i.e. a register. At - which point the store can be unconditional. */ - label = gen_label_rtx (); - emit_cmp_and_jump_insns (target, const0_rtx, NE, NULL, - GET_MODE (target), 1, label); - emit_move_insn (expect, oldval); - emit_label (label); - - return target; -} - -/* Helper function for expand_ifn_atomic_compare_exchange - expand - internal ATOMIC_COMPARE_EXCHANGE call into __atomic_compare_exchange_N - call. The weak parameter must be dropped to match the expected parameter - list and the expected argument changed from value to pointer to memory - slot. */ - -static void -expand_ifn_atomic_compare_exchange_into_call (gcall *call, machine_mode mode) -{ - unsigned int z; - vec *vec; - - vec_alloc (vec, 5); - vec->quick_push (gimple_call_arg (call, 0)); - tree expected = gimple_call_arg (call, 1); - rtx x = assign_stack_temp_for_type (mode, GET_MODE_SIZE (mode), - TREE_TYPE (expected)); - rtx expd = expand_expr (expected, x, mode, EXPAND_NORMAL); - if (expd != x) - emit_move_insn (x, expd); - tree v = make_tree (TREE_TYPE (expected), x); - vec->quick_push (build1 (ADDR_EXPR, - build_pointer_type (TREE_TYPE (expected)), v)); - vec->quick_push (gimple_call_arg (call, 2)); - /* Skip the boolean weak parameter. */ - for (z = 4; z < 6; z++) - vec->quick_push (gimple_call_arg (call, z)); - /* At present we only have BUILT_IN_ATOMIC_COMPARE_EXCHANGE_{1,2,4,8,16}. */ - unsigned int bytes_log2 = exact_log2 (GET_MODE_SIZE (mode).to_constant ()); - gcc_assert (bytes_log2 < 5); - built_in_function fncode - = (built_in_function) ((int) BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1 - + bytes_log2); - tree fndecl = builtin_decl_explicit (fncode); - tree fn = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (fndecl)), - fndecl); - tree exp = build_call_vec (boolean_type_node, fn, vec); - tree lhs = gimple_call_lhs (call); - rtx boolret = expand_call (exp, NULL_RTX, lhs == NULL_TREE); - if (lhs) - { - rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); - if (GET_MODE (boolret) != mode) - boolret = convert_modes (mode, GET_MODE (boolret), boolret, 1); - x = force_reg (mode, x); - write_complex_part (target, boolret, true); - write_complex_part (target, x, false); - } -} - -/* Expand IFN_ATOMIC_COMPARE_EXCHANGE internal function. */ - -void -expand_ifn_atomic_compare_exchange (gcall *call) +/* Expand a call to __builtin_speculation_safe_value_. MODE + represents the size of the first argument to that call, or VOIDmode + if the argument is a pointer. IGNORE will be true if the result + isn't used. */ +static rtx +expand_speculation_safe_value (machine_mode mode, tree exp, rtx target, + bool ignore) { - int size = tree_to_shwi (gimple_call_arg (call, 3)) & 255; - gcc_assert (size == 1 || size == 2 || size == 4 || size == 8 || size == 16); - machine_mode mode = int_mode_for_size (BITS_PER_UNIT * size, 0).require (); - rtx expect, desired, mem, oldval, boolret; - enum memmodel success, failure; - tree lhs; - bool is_weak; - location_t loc - = expansion_point_location_if_in_system_header (gimple_location (call)); + rtx val, failsafe; + unsigned nargs = call_expr_nargs (exp); - success = get_memmodel (gimple_call_arg (call, 4)); - failure = get_memmodel (gimple_call_arg (call, 5)); + tree arg0 = CALL_EXPR_ARG (exp, 0); - if (failure > success) + if (mode == VOIDmode) { - warning_at (loc, OPT_Winvalid_memory_model, - "failure memory model cannot be stronger than success " - "memory model for %<__atomic_compare_exchange%>"); - success = MEMMODEL_SEQ_CST; + mode = TYPE_MODE (TREE_TYPE (arg0)); + gcc_assert (GET_MODE_CLASS (mode) == MODE_INT); } - if (is_mm_release (failure) || is_mm_acq_rel (failure)) - { - warning_at (loc, OPT_Winvalid_memory_model, - "invalid failure memory model for " - "%<__atomic_compare_exchange%>"); - failure = MEMMODEL_SEQ_CST; - success = MEMMODEL_SEQ_CST; - } + val = expand_expr (arg0, NULL_RTX, mode, EXPAND_NORMAL); - if (!flag_inline_atomics) + /* An optional second argument can be used as a failsafe value on + some machines. If it isn't present, then the failsafe value is + assumed to be 0. */ + if (nargs > 1) { - expand_ifn_atomic_compare_exchange_into_call (call, mode); - return; + tree arg1 = CALL_EXPR_ARG (exp, 1); + failsafe = expand_expr (arg1, NULL_RTX, mode, EXPAND_NORMAL); } + else + failsafe = const0_rtx; - /* Expand the operands. */ - mem = get_builtin_sync_mem (gimple_call_arg (call, 0), mode); - - expect = expand_expr_force_mode (gimple_call_arg (call, 1), mode); - desired = expand_expr_force_mode (gimple_call_arg (call, 2), mode); - - is_weak = (tree_to_shwi (gimple_call_arg (call, 3)) & 256) != 0; + /* If the result isn't used, the behavior is undefined. It would be + nice to emit a warning here, but path splitting means this might + happen with legitimate code. So simply drop the builtin + expansion in that case; we've handled any side-effects above. */ + if (ignore) + return const0_rtx; - boolret = NULL; - oldval = NULL; + /* If we don't have a suitable target, create one to hold the result. */ + if (target == NULL || GET_MODE (target) != mode) + target = gen_reg_rtx (mode); - if (!expand_atomic_compare_and_swap (&boolret, &oldval, mem, expect, desired, - is_weak, success, failure)) - { - expand_ifn_atomic_compare_exchange_into_call (call, mode); - return; - } + if (GET_MODE (val) != mode && GET_MODE (val) != VOIDmode) + val = convert_modes (mode, VOIDmode, val, false); - lhs = gimple_call_lhs (call); - if (lhs) - { - rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); - if (GET_MODE (boolret) != mode) - boolret = convert_modes (mode, GET_MODE (boolret), boolret, 1); - write_complex_part (target, boolret, true); - write_complex_part (target, oldval, false); - } + return targetm.speculation_safe_value (mode, target, val, failsafe); } -/* Expand the __atomic_load intrinsic: - TYPE __atomic_load (TYPE *object, enum memmodel) - EXP is the CALL_EXPR. - TARGET is an optional place for us to store the results. */ +/* Expand an expression EXP that calls a built-in function, + with result going to TARGET if that's convenient + (and in mode MODE if that's convenient). + SUBTARGET may be used as the target for computing one of EXP's operands. + IGNORE is nonzero if the value is to be ignored. */ -static rtx -expand_builtin_atomic_load (machine_mode mode, tree exp, rtx target) +rtx +expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, + int ignore) { - rtx mem; - enum memmodel model; - - model = get_memmodel (CALL_EXPR_ARG (exp, 1)); - if (is_mm_release (model) || is_mm_acq_rel (model)) - { - location_t loc - = expansion_point_location_if_in_system_header (input_location); - warning_at (loc, OPT_Winvalid_memory_model, - "invalid memory model for %<__atomic_load%>"); - model = MEMMODEL_SEQ_CST; - } - - if (!flag_inline_atomics) - return NULL_RTX; + tree fndecl = get_callee_fndecl (exp); + machine_mode target_mode = TYPE_MODE (TREE_TYPE (exp)); + int flags; - /* Expand the operand. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); + if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) + return targetm.expand_builtin (exp, target, subtarget, mode, ignore); - return expand_atomic_load (target, mem, model); -} + /* When ASan is enabled, we don't want to expand some memory/string + builtins and rely on libsanitizer's hooks. This allows us to avoid + redundant checks and be sure, that possible overflow will be detected + by ASan. */ + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + if ((flag_sanitize & SANITIZE_ADDRESS) && asan_intercepted_p (fcode)) + return expand_call (exp, target, ignore); -/* Expand the __atomic_store intrinsic: - void __atomic_store (TYPE *object, TYPE desired, enum memmodel) - EXP is the CALL_EXPR. - TARGET is an optional place for us to store the results. */ + /* When not optimizing, generate calls to library functions for a certain + set of builtins. */ + if (!optimize + && !called_as_built_in (fndecl) + && fcode != BUILT_IN_FORK + && fcode != BUILT_IN_EXECL + && fcode != BUILT_IN_EXECV + && fcode != BUILT_IN_EXECLP + && fcode != BUILT_IN_EXECLE + && fcode != BUILT_IN_EXECVP + && fcode != BUILT_IN_EXECVE + && fcode != BUILT_IN_CLEAR_CACHE + && !ALLOCA_FUNCTION_CODE_P (fcode) + && fcode != BUILT_IN_FREE) + return expand_call (exp, target, ignore); -static rtx -expand_builtin_atomic_store (machine_mode mode, tree exp) -{ - rtx mem, val; - enum memmodel model; + /* The built-in function expanders test for target == const0_rtx + to determine whether the function's result will be ignored. */ + if (ignore) + target = const0_rtx; - model = get_memmodel (CALL_EXPR_ARG (exp, 2)); - if (!(is_mm_relaxed (model) || is_mm_seq_cst (model) - || is_mm_release (model))) + /* If the result of a pure or const built-in function is ignored, and + none of its arguments are volatile, we can avoid expanding the + built-in call and just evaluate the arguments for side-effects. */ + if (target == const0_rtx + && ((flags = flags_from_decl_or_type (fndecl)) & (ECF_CONST | ECF_PURE)) + && !(flags & ECF_LOOPING_CONST_OR_PURE)) { - location_t loc - = expansion_point_location_if_in_system_header (input_location); - warning_at (loc, OPT_Winvalid_memory_model, - "invalid memory model for %<__atomic_store%>"); - model = MEMMODEL_SEQ_CST; - } - - if (!flag_inline_atomics) - return NULL_RTX; - - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + bool volatilep = false; + tree arg; + call_expr_arg_iterator iter; - return expand_atomic_store (mem, val, model, false); -} + FOR_EACH_CALL_EXPR_ARG (arg, iter, exp) + if (TREE_THIS_VOLATILE (arg)) + { + volatilep = true; + break; + } -/* Expand the __atomic_fetch_XXX intrinsic: - TYPE __atomic_fetch_XXX (TYPE *object, TYPE val, enum memmodel) - EXP is the CALL_EXPR. - TARGET is an optional place for us to store the results. - CODE is the operation, PLUS, MINUS, ADD, XOR, or IOR. - FETCH_AFTER is true if returning the result of the operation. - FETCH_AFTER is false if returning the value before the operation. - IGNORE is true if the result is not used. - EXT_CALL is the correct builtin for an external call if this cannot be - resolved to an instruction sequence. */ + if (! volatilep) + { + FOR_EACH_CALL_EXPR_ARG (arg, iter, exp) + expand_expr (arg, const0_rtx, VOIDmode, EXPAND_NORMAL); + return const0_rtx; + } + } -static rtx -expand_builtin_atomic_fetch_op (machine_mode mode, tree exp, rtx target, - enum rtx_code code, bool fetch_after, - bool ignore, enum built_in_function ext_call) -{ - rtx val, mem, ret; - enum memmodel model; - tree fndecl; - tree addr; + switch (fcode) + { + CASE_FLT_FN (BUILT_IN_FABS): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_FABS): + case BUILT_IN_FABSD32: + case BUILT_IN_FABSD64: + case BUILT_IN_FABSD128: + target = expand_builtin_fabs (exp, target, subtarget); + if (target) + return target; + break; - model = get_memmodel (CALL_EXPR_ARG (exp, 2)); + CASE_FLT_FN (BUILT_IN_COPYSIGN): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_COPYSIGN): + target = expand_builtin_copysign (exp, target, subtarget); + if (target) + return target; + break; - /* Expand the operands. */ - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - val = expand_expr_force_mode (CALL_EXPR_ARG (exp, 1), mode); + /* Just do a normal library call if we were unable to fold + the values. */ + CASE_FLT_FN (BUILT_IN_CABS): + break; - /* Only try generating instructions if inlining is turned on. */ - if (flag_inline_atomics) - { - ret = expand_atomic_fetch_op (target, mem, val, code, model, fetch_after); - if (ret) - return ret; - } + CASE_FLT_FN (BUILT_IN_FMA): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_FMA): + target = expand_builtin_mathfn_ternary (exp, target, subtarget); + if (target) + return target; + break; - /* Return if a different routine isn't needed for the library call. */ - if (ext_call == BUILT_IN_NONE) - return NULL_RTX; + CASE_FLT_FN (BUILT_IN_ILOGB): + if (! flag_unsafe_math_optimizations) + break; + gcc_fallthrough (); + CASE_FLT_FN (BUILT_IN_ISINF): + CASE_FLT_FN (BUILT_IN_FINITE): + case BUILT_IN_ISFINITE: + case BUILT_IN_ISNORMAL: + target = expand_builtin_interclass_mathfn (exp, target); + if (target) + return target; + break; - /* Change the call to the specified function. */ - fndecl = get_callee_fndecl (exp); - addr = CALL_EXPR_FN (exp); - STRIP_NOPS (addr); + CASE_FLT_FN (BUILT_IN_ICEIL): + CASE_FLT_FN (BUILT_IN_LCEIL): + CASE_FLT_FN (BUILT_IN_LLCEIL): + CASE_FLT_FN (BUILT_IN_LFLOOR): + CASE_FLT_FN (BUILT_IN_IFLOOR): + CASE_FLT_FN (BUILT_IN_LLFLOOR): + target = expand_builtin_int_roundingfn (exp, target); + if (target) + return target; + break; - gcc_assert (TREE_OPERAND (addr, 0) == fndecl); - TREE_OPERAND (addr, 0) = builtin_decl_explicit (ext_call); + CASE_FLT_FN (BUILT_IN_IRINT): + CASE_FLT_FN (BUILT_IN_LRINT): + CASE_FLT_FN (BUILT_IN_LLRINT): + CASE_FLT_FN (BUILT_IN_IROUND): + CASE_FLT_FN (BUILT_IN_LROUND): + CASE_FLT_FN (BUILT_IN_LLROUND): + target = expand_builtin_int_roundingfn_2 (exp, target); + if (target) + return target; + break; - /* If we will emit code after the call, the call cannot be a tail call. - If it is emitted as a tail call, a barrier is emitted after it, and - then all trailing code is removed. */ - if (!ignore) - CALL_EXPR_TAILCALL (exp) = 0; + CASE_FLT_FN (BUILT_IN_POWI): + target = expand_builtin_powi (exp, target); + if (target) + return target; + break; - /* Expand the call here so we can emit trailing code. */ - ret = expand_call (exp, target, ignore); + CASE_FLT_FN (BUILT_IN_CEXPI): + target = expand_builtin_cexpi (exp, target); + gcc_assert (target); + return target; - /* Replace the original function just in case it matters. */ - TREE_OPERAND (addr, 0) = fndecl; + CASE_FLT_FN (BUILT_IN_SIN): + CASE_FLT_FN (BUILT_IN_COS): + if (! flag_unsafe_math_optimizations) + break; + target = expand_builtin_mathfn_3 (exp, target, subtarget); + if (target) + return target; + break; - /* Then issue the arithmetic correction to return the right result. */ - if (!ignore) - { - if (code == NOT) - { - ret = expand_simple_binop (mode, AND, ret, val, NULL_RTX, true, - OPTAB_LIB_WIDEN); - ret = expand_simple_unop (mode, NOT, ret, target, true); - } + CASE_FLT_FN (BUILT_IN_SINCOS): + if (! flag_unsafe_math_optimizations) + break; + target = expand_builtin_sincos (exp); + if (target) + return target; + break; + + case BUILT_IN_APPLY_ARGS: + return expand_builtin_apply_args (); + + /* __builtin_apply (FUNCTION, ARGUMENTS, ARGSIZE) invokes + FUNCTION with a copy of the parameters described by + ARGUMENTS, and ARGSIZE. It returns a block of memory + allocated on the stack into which is stored all the registers + that might possibly be used for returning the result of a + function. ARGUMENTS is the value returned by + __builtin_apply_args. ARGSIZE is the number of bytes of + arguments that must be copied. ??? How should this value be + computed? We'll also need a safe worst case value for varargs + functions. */ + case BUILT_IN_APPLY: + if (!validate_arglist (exp, POINTER_TYPE, + POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) + && !validate_arglist (exp, REFERENCE_TYPE, + POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return const0_rtx; else - ret = expand_simple_binop (mode, code, ret, val, target, true, - OPTAB_LIB_WIDEN); - } - return ret; -} + { + rtx ops[3]; -/* Expand IFN_ATOMIC_BIT_TEST_AND_* internal function. */ + ops[0] = expand_normal (CALL_EXPR_ARG (exp, 0)); + ops[1] = expand_normal (CALL_EXPR_ARG (exp, 1)); + ops[2] = expand_normal (CALL_EXPR_ARG (exp, 2)); -void -expand_ifn_atomic_bit_test_and (gcall *call) -{ - tree ptr = gimple_call_arg (call, 0); - tree bit = gimple_call_arg (call, 1); - tree flag = gimple_call_arg (call, 2); - tree lhs = gimple_call_lhs (call); - enum memmodel model = MEMMODEL_SYNC_SEQ_CST; - machine_mode mode = TYPE_MODE (TREE_TYPE (flag)); - enum rtx_code code; - optab optab; - class expand_operand ops[5]; + return expand_builtin_apply (ops[0], ops[1], ops[2]); + } - gcc_assert (flag_inline_atomics); + /* __builtin_return (RESULT) causes the function to return the + value described by RESULT. RESULT is address of the block of + memory returned by __builtin_apply. */ + case BUILT_IN_RETURN: + if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + expand_builtin_return (expand_normal (CALL_EXPR_ARG (exp, 0))); + return const0_rtx; - if (gimple_call_num_args (call) == 4) - model = get_memmodel (gimple_call_arg (call, 3)); + case BUILT_IN_SAVEREGS: + return expand_builtin_saveregs (); - rtx mem = get_builtin_sync_mem (ptr, mode); - rtx val = expand_expr_force_mode (bit, mode); + case BUILT_IN_VA_ARG_PACK: + /* All valid uses of __builtin_va_arg_pack () are removed during + inlining. */ + error ("invalid use of %<__builtin_va_arg_pack ()%>"); + return const0_rtx; - switch (gimple_call_internal_fn (call)) - { - case IFN_ATOMIC_BIT_TEST_AND_SET: - code = IOR; - optab = atomic_bit_test_and_set_optab; - break; - case IFN_ATOMIC_BIT_TEST_AND_COMPLEMENT: - code = XOR; - optab = atomic_bit_test_and_complement_optab; - break; - case IFN_ATOMIC_BIT_TEST_AND_RESET: - code = AND; - optab = atomic_bit_test_and_reset_optab; - break; - default: - gcc_unreachable (); - } + case BUILT_IN_VA_ARG_PACK_LEN: + /* All valid uses of __builtin_va_arg_pack_len () are removed during + inlining. */ + error ("invalid use of %<__builtin_va_arg_pack_len ()%>"); + return const0_rtx; - if (lhs == NULL_TREE) - { - val = expand_simple_binop (mode, ASHIFT, const1_rtx, - val, NULL_RTX, true, OPTAB_DIRECT); - if (code == AND) - val = expand_simple_unop (mode, NOT, val, NULL_RTX, true); - expand_atomic_fetch_op (const0_rtx, mem, val, code, model, false); - return; - } + /* Return the address of the first anonymous stack arg. */ + case BUILT_IN_NEXT_ARG: + if (fold_builtin_next_arg (exp, false)) + return const0_rtx; + return expand_builtin_next_arg (); - rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); - enum insn_code icode = direct_optab_handler (optab, mode); - gcc_assert (icode != CODE_FOR_nothing); - create_output_operand (&ops[0], target, mode); - create_fixed_operand (&ops[1], mem); - create_convert_operand_to (&ops[2], val, mode, true); - create_integer_operand (&ops[3], model); - create_integer_operand (&ops[4], integer_onep (flag)); - if (maybe_expand_insn (icode, 5, ops)) - return; + case BUILT_IN_CLEAR_CACHE: + expand_builtin___clear_cache (exp); + return const0_rtx; - rtx bitval = val; - val = expand_simple_binop (mode, ASHIFT, const1_rtx, - val, NULL_RTX, true, OPTAB_DIRECT); - rtx maskval = val; - if (code == AND) - val = expand_simple_unop (mode, NOT, val, NULL_RTX, true); - rtx result = expand_atomic_fetch_op (gen_reg_rtx (mode), mem, val, - code, model, false); - if (integer_onep (flag)) - { - result = expand_simple_binop (mode, ASHIFTRT, result, bitval, - NULL_RTX, true, OPTAB_DIRECT); - result = expand_simple_binop (mode, AND, result, const1_rtx, target, - true, OPTAB_DIRECT); - } - else - result = expand_simple_binop (mode, AND, result, maskval, target, true, - OPTAB_DIRECT); - if (result != target) - emit_move_insn (target, result); -} + case BUILT_IN_CLASSIFY_TYPE: + return expand_builtin_classify_type (exp); -/* Expand an atomic clear operation. - void _atomic_clear (BOOL *obj, enum memmodel) - EXP is the call expression. */ + case BUILT_IN_CONSTANT_P: + return const0_rtx; -static rtx -expand_builtin_atomic_clear (tree exp) -{ - machine_mode mode; - rtx mem, ret; - enum memmodel model; + case BUILT_IN_FRAME_ADDRESS: + case BUILT_IN_RETURN_ADDRESS: + return expand_builtin_frame_address (fndecl, exp); - mode = int_mode_for_size (BOOL_TYPE_SIZE, 0).require (); - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - model = get_memmodel (CALL_EXPR_ARG (exp, 1)); + /* Returns the address of the area where the structure is returned. + 0 otherwise. */ + case BUILT_IN_AGGREGATE_INCOMING_ADDRESS: + if (call_expr_nargs (exp) != 0 + || ! AGGREGATE_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))) + || !MEM_P (DECL_RTL (DECL_RESULT (current_function_decl)))) + return const0_rtx; + else + return XEXP (DECL_RTL (DECL_RESULT (current_function_decl)), 0); - if (is_mm_consume (model) || is_mm_acquire (model) || is_mm_acq_rel (model)) - { - location_t loc - = expansion_point_location_if_in_system_header (input_location); - warning_at (loc, OPT_Winvalid_memory_model, - "invalid memory model for %<__atomic_store%>"); - model = MEMMODEL_SEQ_CST; - } + CASE_BUILT_IN_ALLOCA: + target = expand_builtin_alloca (exp); + if (target) + return target; + break; - /* Try issuing an __atomic_store, and allow fallback to __sync_lock_release. - Failing that, a store is issued by __atomic_store. The only way this can - fail is if the bool type is larger than a word size. Unlikely, but - handle it anyway for completeness. Assume a single threaded model since - there is no atomic support in this case, and no barriers are required. */ - ret = expand_atomic_store (mem, const0_rtx, model, true); - if (!ret) - emit_move_insn (mem, const0_rtx); - return const0_rtx; -} + case BUILT_IN_ASAN_ALLOCAS_UNPOISON: + return expand_asan_emit_allocas_unpoison (exp); -/* Expand an atomic test_and_set operation. - bool _atomic_test_and_set (BOOL *obj, enum memmodel) - EXP is the call expression. */ + case BUILT_IN_STACK_SAVE: + return expand_stack_save (); -static rtx -expand_builtin_atomic_test_and_set (tree exp, rtx target) -{ - rtx mem; - enum memmodel model; - machine_mode mode; + case BUILT_IN_STACK_RESTORE: + expand_stack_restore (CALL_EXPR_ARG (exp, 0)); + return const0_rtx; - mode = int_mode_for_size (BOOL_TYPE_SIZE, 0).require (); - mem = get_builtin_sync_mem (CALL_EXPR_ARG (exp, 0), mode); - model = get_memmodel (CALL_EXPR_ARG (exp, 1)); + case BUILT_IN_BSWAP16: + case BUILT_IN_BSWAP32: + case BUILT_IN_BSWAP64: + case BUILT_IN_BSWAP128: + target = expand_builtin_bswap (target_mode, exp, target, subtarget); + if (target) + return target; + break; - return expand_atomic_test_and_set (target, mem, model); -} + CASE_INT_FN (BUILT_IN_FFS): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, ffs_optab); + if (target) + return target; + break; + CASE_INT_FN (BUILT_IN_CLZ): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, clz_optab); + if (target) + return target; + break; -/* Return true if (optional) argument ARG1 of size ARG0 is always lock free on - this architecture. If ARG1 is NULL, use typical alignment for size ARG0. */ + CASE_INT_FN (BUILT_IN_CTZ): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, ctz_optab); + if (target) + return target; + break; -static tree -fold_builtin_atomic_always_lock_free (tree arg0, tree arg1) -{ - int size; - machine_mode mode; - unsigned int mode_align, type_align; + CASE_INT_FN (BUILT_IN_CLRSB): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, clrsb_optab); + if (target) + return target; + break; - if (TREE_CODE (arg0) != INTEGER_CST) - return NULL_TREE; + CASE_INT_FN (BUILT_IN_POPCOUNT): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, popcount_optab); + if (target) + return target; + break; - /* We need a corresponding integer mode for the access to be lock-free. */ - size = INTVAL (expand_normal (arg0)) * BITS_PER_UNIT; - if (!int_mode_for_size (size, 0).exists (&mode)) - return boolean_false_node; + CASE_INT_FN (BUILT_IN_PARITY): + target = expand_builtin_unop (target_mode, exp, target, + subtarget, parity_optab); + if (target) + return target; + break; - mode_align = GET_MODE_ALIGNMENT (mode); + case BUILT_IN_STRLEN: + target = expand_builtin_strlen (exp, target, target_mode); + if (target) + return target; + break; - if (TREE_CODE (arg1) == INTEGER_CST) - { - unsigned HOST_WIDE_INT val = UINTVAL (expand_normal (arg1)); + case BUILT_IN_STRNLEN: + target = expand_builtin_strnlen (exp, target, target_mode); + if (target) + return target; + break; - /* Either this argument is null, or it's a fake pointer encoding - the alignment of the object. */ - val = least_bit_hwi (val); - val *= BITS_PER_UNIT; + case BUILT_IN_STRCAT: + target = expand_builtin_strcat (exp); + if (target) + return target; + break; - if (val == 0 || mode_align < val) - type_align = mode_align; - else - type_align = val; - } - else - { - tree ttype = TREE_TYPE (arg1); + case BUILT_IN_GETTEXT: + case BUILT_IN_PUTS: + case BUILT_IN_PUTS_UNLOCKED: + case BUILT_IN_STRDUP: + if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + check_read_access (exp, CALL_EXPR_ARG (exp, 0)); + break; - /* This function is usually invoked and folded immediately by the front - end before anything else has a chance to look at it. The pointer - parameter at this point is usually cast to a void *, so check for that - and look past the cast. */ - if (CONVERT_EXPR_P (arg1) - && POINTER_TYPE_P (ttype) - && VOID_TYPE_P (TREE_TYPE (ttype)) - && POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (arg1, 0)))) - arg1 = TREE_OPERAND (arg1, 0); + case BUILT_IN_INDEX: + case BUILT_IN_RINDEX: + case BUILT_IN_STRCHR: + case BUILT_IN_STRRCHR: + if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + check_read_access (exp, CALL_EXPR_ARG (exp, 0)); + break; - ttype = TREE_TYPE (arg1); - gcc_assert (POINTER_TYPE_P (ttype)); + case BUILT_IN_FPUTS: + case BUILT_IN_FPUTS_UNLOCKED: + if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + check_read_access (exp, CALL_EXPR_ARG (exp, 0)); + break; - /* Get the underlying type of the object. */ - ttype = TREE_TYPE (ttype); - type_align = TYPE_ALIGN (ttype); - } + case BUILT_IN_STRNDUP: + if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + check_read_access (exp, CALL_EXPR_ARG (exp, 0), CALL_EXPR_ARG (exp, 1)); + break; - /* If the object has smaller alignment, the lock free routines cannot - be used. */ - if (type_align < mode_align) - return boolean_false_node; + case BUILT_IN_STRCASECMP: + case BUILT_IN_STRPBRK: + case BUILT_IN_STRSPN: + case BUILT_IN_STRCSPN: + case BUILT_IN_STRSTR: + if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + { + check_read_access (exp, CALL_EXPR_ARG (exp, 0)); + check_read_access (exp, CALL_EXPR_ARG (exp, 1)); + } + break; - /* Check if a compare_and_swap pattern exists for the mode which represents - the required size. The pattern is not allowed to fail, so the existence - of the pattern indicates support is present. Also require that an - atomic load exists for the required size. */ - if (can_compare_and_swap_p (mode, true) && can_atomic_load_p (mode)) - return boolean_true_node; - else - return boolean_false_node; -} - -/* Return true if the parameters to call EXP represent an object which will - always generate lock free instructions. The first argument represents the - size of the object, and the second parameter is a pointer to the object - itself. If NULL is passed for the object, then the result is based on - typical alignment for an object of the specified size. Otherwise return - false. */ + case BUILT_IN_STRCPY: + target = expand_builtin_strcpy (exp, target); + if (target) + return target; + break; -static rtx -expand_builtin_atomic_always_lock_free (tree exp) -{ - tree size; - tree arg0 = CALL_EXPR_ARG (exp, 0); - tree arg1 = CALL_EXPR_ARG (exp, 1); + case BUILT_IN_STRNCAT: + target = expand_builtin_strncat (exp, target); + if (target) + return target; + break; - if (TREE_CODE (arg0) != INTEGER_CST) - { - error ("non-constant argument 1 to %qs", "__atomic_always_lock_free"); - return const0_rtx; - } + case BUILT_IN_STRNCPY: + target = expand_builtin_strncpy (exp, target); + if (target) + return target; + break; - size = fold_builtin_atomic_always_lock_free (arg0, arg1); - if (size == boolean_true_node) - return const1_rtx; - return const0_rtx; -} + case BUILT_IN_STPCPY: + target = expand_builtin_stpcpy (exp, target, mode); + if (target) + return target; + break; -/* Return a one or zero if it can be determined that object ARG1 of size ARG - is lock free on this architecture. */ + case BUILT_IN_STPNCPY: + target = expand_builtin_stpncpy (exp, target); + if (target) + return target; + break; -static tree -fold_builtin_atomic_is_lock_free (tree arg0, tree arg1) -{ - if (!flag_inline_atomics) - return NULL_TREE; - - /* If it isn't always lock free, don't generate a result. */ - if (fold_builtin_atomic_always_lock_free (arg0, arg1) == boolean_true_node) - return boolean_true_node; + case BUILT_IN_MEMCHR: + target = expand_builtin_memchr (exp, target); + if (target) + return target; + break; - return NULL_TREE; -} + case BUILT_IN_MEMCPY: + target = expand_builtin_memcpy (exp, target); + if (target) + return target; + break; -/* Return true if the parameters to call EXP represent an object which will - always generate lock free instructions. The first argument represents the - size of the object, and the second parameter is a pointer to the object - itself. If NULL is passed for the object, then the result is based on - typical alignment for an object of the specified size. Otherwise return - NULL*/ + case BUILT_IN_MEMMOVE: + target = expand_builtin_memmove (exp, target); + if (target) + return target; + break; -static rtx -expand_builtin_atomic_is_lock_free (tree exp) -{ - tree size; - tree arg0 = CALL_EXPR_ARG (exp, 0); - tree arg1 = CALL_EXPR_ARG (exp, 1); + case BUILT_IN_MEMPCPY: + target = expand_builtin_mempcpy (exp, target); + if (target) + return target; + break; - if (!INTEGRAL_TYPE_P (TREE_TYPE (arg0))) - { - error ("non-integer argument 1 to %qs", "__atomic_is_lock_free"); - return NULL_RTX; - } + case BUILT_IN_MEMSET: + target = expand_builtin_memset (exp, target, mode); + if (target) + return target; + break; - if (!flag_inline_atomics) - return NULL_RTX; + case BUILT_IN_BZERO: + target = expand_builtin_bzero (exp); + if (target) + return target; + break; - /* If the value is known at compile time, return the RTX for it. */ - size = fold_builtin_atomic_is_lock_free (arg0, arg1); - if (size == boolean_true_node) - return const1_rtx; + /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it + back to a BUILT_IN_STRCMP. Remember to delete the 3rd parameter + when changing it to a strcmp call. */ + case BUILT_IN_STRCMP_EQ: + target = expand_builtin_memcmp (exp, target, true); + if (target) + return target; - return NULL_RTX; -} + /* Change this call back to a BUILT_IN_STRCMP. */ + TREE_OPERAND (exp, 1) + = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRCMP)); -/* Expand the __atomic_thread_fence intrinsic: - void __atomic_thread_fence (enum memmodel) - EXP is the CALL_EXPR. */ + /* Delete the last parameter. */ + unsigned int i; + vec *arg_vec; + vec_alloc (arg_vec, 2); + for (i = 0; i < 2; i++) + arg_vec->quick_push (CALL_EXPR_ARG (exp, i)); + exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), arg_vec); + /* FALLTHROUGH */ -static void -expand_builtin_atomic_thread_fence (tree exp) -{ - enum memmodel model = get_memmodel (CALL_EXPR_ARG (exp, 0)); - expand_mem_thread_fence (model); -} + case BUILT_IN_STRCMP: + target = expand_builtin_strcmp (exp, target); + if (target) + return target; + break; -/* Expand the __atomic_signal_fence intrinsic: - void __atomic_signal_fence (enum memmodel) - EXP is the CALL_EXPR. */ + /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it + back to a BUILT_IN_STRNCMP. */ + case BUILT_IN_STRNCMP_EQ: + target = expand_builtin_memcmp (exp, target, true); + if (target) + return target; -static void -expand_builtin_atomic_signal_fence (tree exp) -{ - enum memmodel model = get_memmodel (CALL_EXPR_ARG (exp, 0)); - expand_mem_signal_fence (model); -} + /* Change it back to a BUILT_IN_STRNCMP. */ + TREE_OPERAND (exp, 1) + = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRNCMP)); + /* FALLTHROUGH */ -/* Expand the __sync_synchronize intrinsic. */ + case BUILT_IN_STRNCMP: + target = expand_builtin_strncmp (exp, target, mode); + if (target) + return target; + break; -static void -expand_builtin_sync_synchronize (void) -{ - expand_mem_thread_fence (MEMMODEL_SYNC_SEQ_CST); -} + case BUILT_IN_BCMP: + case BUILT_IN_MEMCMP: + case BUILT_IN_MEMCMP_EQ: + target = expand_builtin_memcmp (exp, target, fcode == BUILT_IN_MEMCMP_EQ); + if (target) + return target; + if (fcode == BUILT_IN_MEMCMP_EQ) + { + tree newdecl = builtin_decl_explicit (BUILT_IN_MEMCMP); + TREE_OPERAND (exp, 1) = build_fold_addr_expr (newdecl); + } + break; -static rtx -expand_builtin_thread_pointer (tree exp, rtx target) -{ - enum insn_code icode; - if (!validate_arglist (exp, VOID_TYPE)) - return const0_rtx; - icode = direct_optab_handler (get_thread_pointer_optab, Pmode); - if (icode != CODE_FOR_nothing) - { - class expand_operand op; - /* If the target is not sutitable then create a new target. */ - if (target == NULL_RTX - || !REG_P (target) - || GET_MODE (target) != Pmode) - target = gen_reg_rtx (Pmode); - create_output_operand (&op, target, Pmode); - expand_insn (icode, 1, &op); - return target; - } - error ("%<__builtin_thread_pointer%> is not supported on this target"); - return const0_rtx; -} + case BUILT_IN_SETJMP: + /* This should have been lowered to the builtins below. */ + gcc_unreachable (); -static void -expand_builtin_set_thread_pointer (tree exp) -{ - enum insn_code icode; - if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - return; - icode = direct_optab_handler (set_thread_pointer_optab, Pmode); - if (icode != CODE_FOR_nothing) - { - class expand_operand op; - rtx val = expand_expr (CALL_EXPR_ARG (exp, 0), NULL_RTX, - Pmode, EXPAND_NORMAL); - create_input_operand (&op, val, Pmode); - expand_insn (icode, 1, &op); - return; - } - error ("%<__builtin_set_thread_pointer%> is not supported on this target"); -} + case BUILT_IN_SETJMP_SETUP: + /* __builtin_setjmp_setup is passed a pointer to an array of five words + and the receiver label. */ + if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + { + rtx buf_addr = expand_expr (CALL_EXPR_ARG (exp, 0), subtarget, + VOIDmode, EXPAND_NORMAL); + tree label = TREE_OPERAND (CALL_EXPR_ARG (exp, 1), 0); + rtx_insn *label_r = label_rtx (label); - -/* Emit code to restore the current value of stack. */ + /* This is copied from the handling of non-local gotos. */ + expand_builtin_setjmp_setup (buf_addr, label_r); + nonlocal_goto_handler_labels + = gen_rtx_INSN_LIST (VOIDmode, label_r, + nonlocal_goto_handler_labels); + /* ??? Do not let expand_label treat us as such since we would + not want to be both on the list of non-local labels and on + the list of forced labels. */ + FORCED_LABEL (label) = 0; + return const0_rtx; + } + break; -static void -expand_stack_restore (tree var) -{ - rtx_insn *prev; - rtx sa = expand_normal (var); - - sa = convert_memory_address (Pmode, sa); - - prev = get_last_insn (); - emit_stack_restore (SAVE_BLOCK, sa); + case BUILT_IN_SETJMP_RECEIVER: + /* __builtin_setjmp_receiver is passed the receiver label. */ + if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + { + tree label = TREE_OPERAND (CALL_EXPR_ARG (exp, 0), 0); + rtx_insn *label_r = label_rtx (label); - record_new_stack_level (); + expand_builtin_setjmp_receiver (label_r); + return const0_rtx; + } + break; - fixup_args_size_notes (prev, get_last_insn (), 0); -} + /* __builtin_longjmp is passed a pointer to an array of five words. + It's similar to the C library longjmp function but works with + __builtin_setjmp above. */ + case BUILT_IN_LONGJMP: + if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + { + rtx buf_addr = expand_expr (CALL_EXPR_ARG (exp, 0), subtarget, + VOIDmode, EXPAND_NORMAL); + rtx value = expand_normal (CALL_EXPR_ARG (exp, 1)); -/* Emit code to save the current value of stack. */ + if (value != const1_rtx) + { + error ("%<__builtin_longjmp%> second argument must be 1"); + return const0_rtx; + } -static rtx -expand_stack_save (void) -{ - rtx ret = NULL_RTX; + expand_builtin_longjmp (buf_addr, value); + return const0_rtx; + } + break; - emit_stack_save (SAVE_BLOCK, &ret); - return ret; -} + case BUILT_IN_NONLOCAL_GOTO: + target = expand_builtin_nonlocal_goto (exp); + if (target) + return target; + break; -/* Emit code to get the openacc gang, worker or vector id or size. */ + /* This updates the setjmp buffer that is its argument with the value + of the current stack pointer. */ + case BUILT_IN_UPDATE_SETJMP_BUF: + if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + { + rtx buf_addr + = expand_normal (CALL_EXPR_ARG (exp, 0)); -static rtx -expand_builtin_goacc_parlevel_id_size (tree exp, rtx target, int ignore) -{ - const char *name; - rtx fallback_retval; - rtx_insn *(*gen_fn) (rtx, rtx); - switch (DECL_FUNCTION_CODE (get_callee_fndecl (exp))) - { - case BUILT_IN_GOACC_PARLEVEL_ID: - name = "__builtin_goacc_parlevel_id"; - fallback_retval = const0_rtx; - gen_fn = targetm.gen_oacc_dim_pos; - break; - case BUILT_IN_GOACC_PARLEVEL_SIZE: - name = "__builtin_goacc_parlevel_size"; - fallback_retval = const1_rtx; - gen_fn = targetm.gen_oacc_dim_size; + expand_builtin_update_setjmp_buf (buf_addr); + return const0_rtx; + } break; - default: - gcc_unreachable (); - } - if (oacc_get_fn_attrib (current_function_decl) == NULL_TREE) - { - error ("%qs only supported in OpenACC code", name); + case BUILT_IN_TRAP: + expand_builtin_trap (); return const0_rtx; - } - tree arg = CALL_EXPR_ARG (exp, 0); - if (TREE_CODE (arg) != INTEGER_CST) - { - error ("non-constant argument 0 to %qs", name); + case BUILT_IN_UNREACHABLE: + expand_builtin_unreachable (); return const0_rtx; - } - int dim = TREE_INT_CST_LOW (arg); - switch (dim) - { - case GOMP_DIM_GANG: - case GOMP_DIM_WORKER: - case GOMP_DIM_VECTOR: + CASE_FLT_FN (BUILT_IN_SIGNBIT): + case BUILT_IN_SIGNBITD32: + case BUILT_IN_SIGNBITD64: + case BUILT_IN_SIGNBITD128: + target = expand_builtin_signbit (exp, target); + if (target) + return target; break; - default: - error ("illegal argument 0 to %qs", name); - return const0_rtx; - } - if (ignore) - return target; + /* Various hooks for the DWARF 2 __throw routine. */ + case BUILT_IN_UNWIND_INIT: + expand_builtin_unwind_init (); + return const0_rtx; + case BUILT_IN_DWARF_CFA: + return virtual_cfa_rtx; +#ifdef DWARF2_UNWIND_INFO + case BUILT_IN_DWARF_SP_COLUMN: + return expand_builtin_dwarf_sp_column (); + case BUILT_IN_INIT_DWARF_REG_SIZES: + expand_builtin_init_dwarf_reg_sizes (CALL_EXPR_ARG (exp, 0)); + return const0_rtx; +#endif + case BUILT_IN_FROB_RETURN_ADDR: + return expand_builtin_frob_return_addr (CALL_EXPR_ARG (exp, 0)); + case BUILT_IN_EXTRACT_RETURN_ADDR: + return expand_builtin_extract_return_addr (CALL_EXPR_ARG (exp, 0)); + case BUILT_IN_EH_RETURN: + expand_builtin_eh_return (CALL_EXPR_ARG (exp, 0), + CALL_EXPR_ARG (exp, 1)); + return const0_rtx; + case BUILT_IN_EH_RETURN_DATA_REGNO: + return expand_builtin_eh_return_data_regno (exp); + case BUILT_IN_EXTEND_POINTER: + return expand_builtin_extend_pointer (CALL_EXPR_ARG (exp, 0)); + case BUILT_IN_EH_POINTER: + return expand_builtin_eh_pointer (exp); + case BUILT_IN_EH_FILTER: + return expand_builtin_eh_filter (exp); + case BUILT_IN_EH_COPY_VALUES: + return expand_builtin_eh_copy_values (exp); - if (target == NULL_RTX) - target = gen_reg_rtx (TYPE_MODE (TREE_TYPE (exp))); + case BUILT_IN_VA_START: + return expand_builtin_va_start (exp); + case BUILT_IN_VA_END: + return expand_builtin_va_end (exp); + case BUILT_IN_VA_COPY: + return expand_builtin_va_copy (exp); + case BUILT_IN_EXPECT: + return expand_builtin_expect (exp, target); + case BUILT_IN_EXPECT_WITH_PROBABILITY: + return expand_builtin_expect_with_probability (exp, target); + case BUILT_IN_ASSUME_ALIGNED: + return expand_builtin_assume_aligned (exp, target); + case BUILT_IN_PREFETCH: + expand_builtin_prefetch (exp); + return const0_rtx; - if (!targetm.have_oacc_dim_size ()) - { - emit_move_insn (target, fallback_retval); - return target; - } + case BUILT_IN_INIT_TRAMPOLINE: + return expand_builtin_init_trampoline (exp, true); + case BUILT_IN_INIT_HEAP_TRAMPOLINE: + return expand_builtin_init_trampoline (exp, false); + case BUILT_IN_ADJUST_TRAMPOLINE: + return expand_builtin_adjust_trampoline (exp); - rtx reg = MEM_P (target) ? gen_reg_rtx (GET_MODE (target)) : target; - emit_insn (gen_fn (reg, GEN_INT (dim))); - if (reg != target) - emit_move_insn (target, reg); + case BUILT_IN_INIT_DESCRIPTOR: + return expand_builtin_init_descriptor (exp); + case BUILT_IN_ADJUST_DESCRIPTOR: + return expand_builtin_adjust_descriptor (exp); - return target; -} + case BUILT_IN_FORK: + case BUILT_IN_EXECL: + case BUILT_IN_EXECV: + case BUILT_IN_EXECLP: + case BUILT_IN_EXECLE: + case BUILT_IN_EXECVP: + case BUILT_IN_EXECVE: + target = expand_builtin_fork_or_exec (fndecl, exp, target, ignore); + if (target) + return target; + break; -/* Expand a string compare operation using a sequence of char comparison - to get rid of the calling overhead, with result going to TARGET if - that's convenient. + case BUILT_IN_SYNC_FETCH_AND_ADD_1: + case BUILT_IN_SYNC_FETCH_AND_ADD_2: + case BUILT_IN_SYNC_FETCH_AND_ADD_4: + case BUILT_IN_SYNC_FETCH_AND_ADD_8: + case BUILT_IN_SYNC_FETCH_AND_ADD_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_ADD_1); + target = expand_builtin_sync_operation (mode, exp, PLUS, false, target); + if (target) + return target; + break; - VAR_STR is the variable string source; - CONST_STR is the constant string source; - LENGTH is the number of chars to compare; - CONST_STR_N indicates which source string is the constant string; - IS_MEMCMP indicates whether it's a memcmp or strcmp. - - to: (assume const_str_n is 2, i.e., arg2 is a constant string) + case BUILT_IN_SYNC_FETCH_AND_SUB_1: + case BUILT_IN_SYNC_FETCH_AND_SUB_2: + case BUILT_IN_SYNC_FETCH_AND_SUB_4: + case BUILT_IN_SYNC_FETCH_AND_SUB_8: + case BUILT_IN_SYNC_FETCH_AND_SUB_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_SUB_1); + target = expand_builtin_sync_operation (mode, exp, MINUS, false, target); + if (target) + return target; + break; - target = (int) (unsigned char) var_str[0] - - (int) (unsigned char) const_str[0]; - if (target != 0) - goto ne_label; - ... - target = (int) (unsigned char) var_str[length - 2] - - (int) (unsigned char) const_str[length - 2]; - if (target != 0) - goto ne_label; - target = (int) (unsigned char) var_str[length - 1] - - (int) (unsigned char) const_str[length - 1]; - ne_label: - */ + case BUILT_IN_SYNC_FETCH_AND_OR_1: + case BUILT_IN_SYNC_FETCH_AND_OR_2: + case BUILT_IN_SYNC_FETCH_AND_OR_4: + case BUILT_IN_SYNC_FETCH_AND_OR_8: + case BUILT_IN_SYNC_FETCH_AND_OR_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_OR_1); + target = expand_builtin_sync_operation (mode, exp, IOR, false, target); + if (target) + return target; + break; -static rtx -inline_string_cmp (rtx target, tree var_str, const char *const_str, - unsigned HOST_WIDE_INT length, - int const_str_n, machine_mode mode) -{ - HOST_WIDE_INT offset = 0; - rtx var_rtx_array - = get_memory_rtx (var_str, build_int_cst (unsigned_type_node,length)); - rtx var_rtx = NULL_RTX; - rtx const_rtx = NULL_RTX; - rtx result = target ? target : gen_reg_rtx (mode); - rtx_code_label *ne_label = gen_label_rtx (); - tree unit_type_node = unsigned_char_type_node; - scalar_int_mode unit_mode - = as_a TYPE_MODE (unit_type_node); - - start_sequence (); + case BUILT_IN_SYNC_FETCH_AND_AND_1: + case BUILT_IN_SYNC_FETCH_AND_AND_2: + case BUILT_IN_SYNC_FETCH_AND_AND_4: + case BUILT_IN_SYNC_FETCH_AND_AND_8: + case BUILT_IN_SYNC_FETCH_AND_AND_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_AND_1); + target = expand_builtin_sync_operation (mode, exp, AND, false, target); + if (target) + return target; + break; - for (unsigned HOST_WIDE_INT i = 0; i < length; i++) - { - var_rtx - = adjust_address (var_rtx_array, TYPE_MODE (unit_type_node), offset); - const_rtx = c_readstr (const_str + offset, unit_mode); - rtx op0 = (const_str_n == 1) ? const_rtx : var_rtx; - rtx op1 = (const_str_n == 1) ? var_rtx : const_rtx; + case BUILT_IN_SYNC_FETCH_AND_XOR_1: + case BUILT_IN_SYNC_FETCH_AND_XOR_2: + case BUILT_IN_SYNC_FETCH_AND_XOR_4: + case BUILT_IN_SYNC_FETCH_AND_XOR_8: + case BUILT_IN_SYNC_FETCH_AND_XOR_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_XOR_1); + target = expand_builtin_sync_operation (mode, exp, XOR, false, target); + if (target) + return target; + break; - op0 = convert_modes (mode, unit_mode, op0, 1); - op1 = convert_modes (mode, unit_mode, op1, 1); - result = expand_simple_binop (mode, MINUS, op0, op1, - result, 1, OPTAB_WIDEN); - if (i < length - 1) - emit_cmp_and_jump_insns (result, CONST0_RTX (mode), NE, NULL_RTX, - mode, true, ne_label); - offset += GET_MODE_SIZE (unit_mode); - } + case BUILT_IN_SYNC_FETCH_AND_NAND_1: + case BUILT_IN_SYNC_FETCH_AND_NAND_2: + case BUILT_IN_SYNC_FETCH_AND_NAND_4: + case BUILT_IN_SYNC_FETCH_AND_NAND_8: + case BUILT_IN_SYNC_FETCH_AND_NAND_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_NAND_1); + target = expand_builtin_sync_operation (mode, exp, NOT, false, target); + if (target) + return target; + break; - emit_label (ne_label); - rtx_insn *insns = get_insns (); - end_sequence (); - emit_insn (insns); + case BUILT_IN_SYNC_ADD_AND_FETCH_1: + case BUILT_IN_SYNC_ADD_AND_FETCH_2: + case BUILT_IN_SYNC_ADD_AND_FETCH_4: + case BUILT_IN_SYNC_ADD_AND_FETCH_8: + case BUILT_IN_SYNC_ADD_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_ADD_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, PLUS, true, target); + if (target) + return target; + break; - return result; -} + case BUILT_IN_SYNC_SUB_AND_FETCH_1: + case BUILT_IN_SYNC_SUB_AND_FETCH_2: + case BUILT_IN_SYNC_SUB_AND_FETCH_4: + case BUILT_IN_SYNC_SUB_AND_FETCH_8: + case BUILT_IN_SYNC_SUB_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_SUB_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, MINUS, true, target); + if (target) + return target; + break; -/* Inline expansion of a call to str(n)cmp and memcmp, with result going - to TARGET if that's convenient. - If the call is not been inlined, return NULL_RTX. */ + case BUILT_IN_SYNC_OR_AND_FETCH_1: + case BUILT_IN_SYNC_OR_AND_FETCH_2: + case BUILT_IN_SYNC_OR_AND_FETCH_4: + case BUILT_IN_SYNC_OR_AND_FETCH_8: + case BUILT_IN_SYNC_OR_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_OR_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, IOR, true, target); + if (target) + return target; + break; -static rtx -inline_expand_builtin_bytecmp (tree exp, rtx target) -{ - tree fndecl = get_callee_fndecl (exp); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - bool is_ncmp = (fcode == BUILT_IN_STRNCMP || fcode == BUILT_IN_MEMCMP); + case BUILT_IN_SYNC_AND_AND_FETCH_1: + case BUILT_IN_SYNC_AND_AND_FETCH_2: + case BUILT_IN_SYNC_AND_AND_FETCH_4: + case BUILT_IN_SYNC_AND_AND_FETCH_8: + case BUILT_IN_SYNC_AND_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_AND_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, AND, true, target); + if (target) + return target; + break; - /* Do NOT apply this inlining expansion when optimizing for size or - optimization level below 2. */ - if (optimize < 2 || optimize_insn_for_size_p ()) - return NULL_RTX; + case BUILT_IN_SYNC_XOR_AND_FETCH_1: + case BUILT_IN_SYNC_XOR_AND_FETCH_2: + case BUILT_IN_SYNC_XOR_AND_FETCH_4: + case BUILT_IN_SYNC_XOR_AND_FETCH_8: + case BUILT_IN_SYNC_XOR_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_XOR_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, XOR, true, target); + if (target) + return target; + break; - gcc_checking_assert (fcode == BUILT_IN_STRCMP - || fcode == BUILT_IN_STRNCMP - || fcode == BUILT_IN_MEMCMP); + case BUILT_IN_SYNC_NAND_AND_FETCH_1: + case BUILT_IN_SYNC_NAND_AND_FETCH_2: + case BUILT_IN_SYNC_NAND_AND_FETCH_4: + case BUILT_IN_SYNC_NAND_AND_FETCH_8: + case BUILT_IN_SYNC_NAND_AND_FETCH_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_NAND_AND_FETCH_1); + target = expand_builtin_sync_operation (mode, exp, NOT, true, target); + if (target) + return target; + break; - /* On a target where the type of the call (int) has same or narrower presicion - than unsigned char, give up the inlining expansion. */ - if (TYPE_PRECISION (unsigned_char_type_node) - >= TYPE_PRECISION (TREE_TYPE (exp))) - return NULL_RTX; + case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_1: + case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_2: + case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_4: + case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_8: + case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_16: + if (mode == VOIDmode) + mode = TYPE_MODE (boolean_type_node); + if (!target || !register_operand (target, mode)) + target = gen_reg_rtx (mode); - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); - tree len3_tree = is_ncmp ? CALL_EXPR_ARG (exp, 2) : NULL_TREE; + mode = get_builtin_sync_mode + (fcode - BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_1); + target = expand_builtin_compare_and_swap (mode, exp, true, target); + if (target) + return target; + break; - unsigned HOST_WIDE_INT len1 = 0; - unsigned HOST_WIDE_INT len2 = 0; - unsigned HOST_WIDE_INT len3 = 0; + case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1: + case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_2: + case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_4: + case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_8: + case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_16: + mode = get_builtin_sync_mode + (fcode - BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1); + target = expand_builtin_compare_and_swap (mode, exp, false, target); + if (target) + return target; + break; - /* Get the object representation of the initializers of ARG1 and ARG2 - as strings, provided they refer to constant objects, with their byte - sizes in LEN1 and LEN2, respectively. */ - const char *bytes1 = getbyterep (arg1, &len1); - const char *bytes2 = getbyterep (arg2, &len2); + case BUILT_IN_SYNC_LOCK_TEST_AND_SET_1: + case BUILT_IN_SYNC_LOCK_TEST_AND_SET_2: + case BUILT_IN_SYNC_LOCK_TEST_AND_SET_4: + case BUILT_IN_SYNC_LOCK_TEST_AND_SET_8: + case BUILT_IN_SYNC_LOCK_TEST_AND_SET_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_LOCK_TEST_AND_SET_1); + target = expand_builtin_sync_lock_test_and_set (mode, exp, target); + if (target) + return target; + break; - /* Fail if neither argument refers to an initialized constant. */ - if (!bytes1 && !bytes2) - return NULL_RTX; + case BUILT_IN_SYNC_LOCK_RELEASE_1: + case BUILT_IN_SYNC_LOCK_RELEASE_2: + case BUILT_IN_SYNC_LOCK_RELEASE_4: + case BUILT_IN_SYNC_LOCK_RELEASE_8: + case BUILT_IN_SYNC_LOCK_RELEASE_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_LOCK_RELEASE_1); + expand_builtin_sync_lock_release (mode, exp); + return const0_rtx; - if (is_ncmp) - { - /* Fail if the memcmp/strncmp bound is not a constant. */ - if (!tree_fits_uhwi_p (len3_tree)) - return NULL_RTX; + case BUILT_IN_SYNC_SYNCHRONIZE: + expand_builtin_sync_synchronize (); + return const0_rtx; - len3 = tree_to_uhwi (len3_tree); + case BUILT_IN_ATOMIC_EXCHANGE_1: + case BUILT_IN_ATOMIC_EXCHANGE_2: + case BUILT_IN_ATOMIC_EXCHANGE_4: + case BUILT_IN_ATOMIC_EXCHANGE_8: + case BUILT_IN_ATOMIC_EXCHANGE_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_EXCHANGE_1); + target = expand_builtin_atomic_exchange (mode, exp, target); + if (target) + return target; + break; - if (fcode == BUILT_IN_MEMCMP) - { - /* Fail if the memcmp bound is greater than the size of either - of the two constant objects. */ - if ((bytes1 && len1 < len3) - || (bytes2 && len2 < len3)) - return NULL_RTX; - } - } + case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1: + case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_2: + case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_4: + case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_8: + case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_16: + { + unsigned int nargs, z; + vec *vec; - if (fcode != BUILT_IN_MEMCMP) - { - /* For string functions (i.e., strcmp and strncmp) reduce LEN1 - and LEN2 to the length of the nul-terminated string stored - in each. */ - if (bytes1 != NULL) - len1 = strnlen (bytes1, len1) + 1; - if (bytes2 != NULL) - len2 = strnlen (bytes2, len2) + 1; - } + mode = + get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1); + target = expand_builtin_atomic_compare_exchange (mode, exp, target); + if (target) + return target; - /* See inline_string_cmp. */ - int const_str_n; - if (!len1) - const_str_n = 2; - else if (!len2) - const_str_n = 1; - else if (len2 > len1) - const_str_n = 1; - else - const_str_n = 2; - - /* For strncmp only, compute the new bound as the smallest of - the lengths of the two strings (plus 1) and the bound provided - to the function. */ - unsigned HOST_WIDE_INT bound = (const_str_n == 1) ? len1 : len2; - if (is_ncmp && len3 < bound) - bound = len3; - - /* If the bound of the comparison is larger than the threshold, - do nothing. */ - if (bound > (unsigned HOST_WIDE_INT) param_builtin_string_cmp_inline_length) - return NULL_RTX; - - machine_mode mode = TYPE_MODE (TREE_TYPE (exp)); - - /* Now, start inline expansion the call. */ - return inline_string_cmp (target, (const_str_n == 1) ? arg2 : arg1, - (const_str_n == 1) ? bytes1 : bytes2, bound, - const_str_n, mode); -} - -/* Expand a call to __builtin_speculation_safe_value_. MODE - represents the size of the first argument to that call, or VOIDmode - if the argument is a pointer. IGNORE will be true if the result - isn't used. */ -static rtx -expand_speculation_safe_value (machine_mode mode, tree exp, rtx target, - bool ignore) -{ - rtx val, failsafe; - unsigned nargs = call_expr_nargs (exp); - - tree arg0 = CALL_EXPR_ARG (exp, 0); - - if (mode == VOIDmode) - { - mode = TYPE_MODE (TREE_TYPE (arg0)); - gcc_assert (GET_MODE_CLASS (mode) == MODE_INT); - } - - val = expand_expr (arg0, NULL_RTX, mode, EXPAND_NORMAL); - - /* An optional second argument can be used as a failsafe value on - some machines. If it isn't present, then the failsafe value is - assumed to be 0. */ - if (nargs > 1) - { - tree arg1 = CALL_EXPR_ARG (exp, 1); - failsafe = expand_expr (arg1, NULL_RTX, mode, EXPAND_NORMAL); - } - else - failsafe = const0_rtx; - - /* If the result isn't used, the behavior is undefined. It would be - nice to emit a warning here, but path splitting means this might - happen with legitimate code. So simply drop the builtin - expansion in that case; we've handled any side-effects above. */ - if (ignore) - return const0_rtx; - - /* If we don't have a suitable target, create one to hold the result. */ - if (target == NULL || GET_MODE (target) != mode) - target = gen_reg_rtx (mode); - - if (GET_MODE (val) != mode && GET_MODE (val) != VOIDmode) - val = convert_modes (mode, VOIDmode, val, false); - - return targetm.speculation_safe_value (mode, target, val, failsafe); -} - -/* Expand an expression EXP that calls a built-in function, - with result going to TARGET if that's convenient - (and in mode MODE if that's convenient). - SUBTARGET may be used as the target for computing one of EXP's operands. - IGNORE is nonzero if the value is to be ignored. */ - -rtx -expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, - int ignore) -{ - tree fndecl = get_callee_fndecl (exp); - machine_mode target_mode = TYPE_MODE (TREE_TYPE (exp)); - int flags; - - if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) - return targetm.expand_builtin (exp, target, subtarget, mode, ignore); - - /* When ASan is enabled, we don't want to expand some memory/string - builtins and rely on libsanitizer's hooks. This allows us to avoid - redundant checks and be sure, that possible overflow will be detected - by ASan. */ - - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - if ((flag_sanitize & SANITIZE_ADDRESS) && asan_intercepted_p (fcode)) - return expand_call (exp, target, ignore); - - /* When not optimizing, generate calls to library functions for a certain - set of builtins. */ - if (!optimize - && !called_as_built_in (fndecl) - && fcode != BUILT_IN_FORK - && fcode != BUILT_IN_EXECL - && fcode != BUILT_IN_EXECV - && fcode != BUILT_IN_EXECLP - && fcode != BUILT_IN_EXECLE - && fcode != BUILT_IN_EXECVP - && fcode != BUILT_IN_EXECVE - && fcode != BUILT_IN_CLEAR_CACHE - && !ALLOCA_FUNCTION_CODE_P (fcode) - && fcode != BUILT_IN_FREE) - return expand_call (exp, target, ignore); - - /* The built-in function expanders test for target == const0_rtx - to determine whether the function's result will be ignored. */ - if (ignore) - target = const0_rtx; - - /* If the result of a pure or const built-in function is ignored, and - none of its arguments are volatile, we can avoid expanding the - built-in call and just evaluate the arguments for side-effects. */ - if (target == const0_rtx - && ((flags = flags_from_decl_or_type (fndecl)) & (ECF_CONST | ECF_PURE)) - && !(flags & ECF_LOOPING_CONST_OR_PURE)) - { - bool volatilep = false; - tree arg; - call_expr_arg_iterator iter; - - FOR_EACH_CALL_EXPR_ARG (arg, iter, exp) - if (TREE_THIS_VOLATILE (arg)) - { - volatilep = true; - break; - } - - if (! volatilep) - { - FOR_EACH_CALL_EXPR_ARG (arg, iter, exp) - expand_expr (arg, const0_rtx, VOIDmode, EXPAND_NORMAL); - return const0_rtx; - } - } - - switch (fcode) - { - CASE_FLT_FN (BUILT_IN_FABS): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_FABS): - case BUILT_IN_FABSD32: - case BUILT_IN_FABSD64: - case BUILT_IN_FABSD128: - target = expand_builtin_fabs (exp, target, subtarget); - if (target) - return target; - break; - - CASE_FLT_FN (BUILT_IN_COPYSIGN): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_COPYSIGN): - target = expand_builtin_copysign (exp, target, subtarget); - if (target) - return target; - break; - - /* Just do a normal library call if we were unable to fold - the values. */ - CASE_FLT_FN (BUILT_IN_CABS): - break; - - CASE_FLT_FN (BUILT_IN_FMA): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_FMA): - target = expand_builtin_mathfn_ternary (exp, target, subtarget); - if (target) - return target; - break; - - CASE_FLT_FN (BUILT_IN_ILOGB): - if (! flag_unsafe_math_optimizations) + /* If this is turned into an external library call, the weak parameter + must be dropped to match the expected parameter list. */ + nargs = call_expr_nargs (exp); + vec_alloc (vec, nargs - 1); + for (z = 0; z < 3; z++) + vec->quick_push (CALL_EXPR_ARG (exp, z)); + /* Skip the boolean weak parameter. */ + for (z = 4; z < 6; z++) + vec->quick_push (CALL_EXPR_ARG (exp, z)); + exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), vec); break; - gcc_fallthrough (); - CASE_FLT_FN (BUILT_IN_ISINF): - CASE_FLT_FN (BUILT_IN_FINITE): - case BUILT_IN_ISFINITE: - case BUILT_IN_ISNORMAL: - target = expand_builtin_interclass_mathfn (exp, target); - if (target) - return target; - break; - - CASE_FLT_FN (BUILT_IN_ICEIL): - CASE_FLT_FN (BUILT_IN_LCEIL): - CASE_FLT_FN (BUILT_IN_LLCEIL): - CASE_FLT_FN (BUILT_IN_LFLOOR): - CASE_FLT_FN (BUILT_IN_IFLOOR): - CASE_FLT_FN (BUILT_IN_LLFLOOR): - target = expand_builtin_int_roundingfn (exp, target); - if (target) - return target; - break; - - CASE_FLT_FN (BUILT_IN_IRINT): - CASE_FLT_FN (BUILT_IN_LRINT): - CASE_FLT_FN (BUILT_IN_LLRINT): - CASE_FLT_FN (BUILT_IN_IROUND): - CASE_FLT_FN (BUILT_IN_LROUND): - CASE_FLT_FN (BUILT_IN_LLROUND): - target = expand_builtin_int_roundingfn_2 (exp, target); - if (target) - return target; - break; + } - CASE_FLT_FN (BUILT_IN_POWI): - target = expand_builtin_powi (exp, target); + case BUILT_IN_ATOMIC_LOAD_1: + case BUILT_IN_ATOMIC_LOAD_2: + case BUILT_IN_ATOMIC_LOAD_4: + case BUILT_IN_ATOMIC_LOAD_8: + case BUILT_IN_ATOMIC_LOAD_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_LOAD_1); + target = expand_builtin_atomic_load (mode, exp, target); if (target) return target; break; - CASE_FLT_FN (BUILT_IN_CEXPI): - target = expand_builtin_cexpi (exp, target); - gcc_assert (target); - return target; - - CASE_FLT_FN (BUILT_IN_SIN): - CASE_FLT_FN (BUILT_IN_COS): - if (! flag_unsafe_math_optimizations) - break; - target = expand_builtin_mathfn_3 (exp, target, subtarget); + case BUILT_IN_ATOMIC_STORE_1: + case BUILT_IN_ATOMIC_STORE_2: + case BUILT_IN_ATOMIC_STORE_4: + case BUILT_IN_ATOMIC_STORE_8: + case BUILT_IN_ATOMIC_STORE_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_STORE_1); + target = expand_builtin_atomic_store (mode, exp); if (target) - return target; + return const0_rtx; break; - CASE_FLT_FN (BUILT_IN_SINCOS): - if (! flag_unsafe_math_optimizations) - break; - target = expand_builtin_sincos (exp); - if (target) - return target; - break; - - case BUILT_IN_APPLY_ARGS: - return expand_builtin_apply_args (); - - /* __builtin_apply (FUNCTION, ARGUMENTS, ARGSIZE) invokes - FUNCTION with a copy of the parameters described by - ARGUMENTS, and ARGSIZE. It returns a block of memory - allocated on the stack into which is stored all the registers - that might possibly be used for returning the result of a - function. ARGUMENTS is the value returned by - __builtin_apply_args. ARGSIZE is the number of bytes of - arguments that must be copied. ??? How should this value be - computed? We'll also need a safe worst case value for varargs - functions. */ - case BUILT_IN_APPLY: - if (!validate_arglist (exp, POINTER_TYPE, - POINTER_TYPE, INTEGER_TYPE, VOID_TYPE) - && !validate_arglist (exp, REFERENCE_TYPE, - POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return const0_rtx; - else - { - rtx ops[3]; - - ops[0] = expand_normal (CALL_EXPR_ARG (exp, 0)); - ops[1] = expand_normal (CALL_EXPR_ARG (exp, 1)); - ops[2] = expand_normal (CALL_EXPR_ARG (exp, 2)); - - return expand_builtin_apply (ops[0], ops[1], ops[2]); - } - - /* __builtin_return (RESULT) causes the function to return the - value described by RESULT. RESULT is address of the block of - memory returned by __builtin_apply. */ - case BUILT_IN_RETURN: - if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - expand_builtin_return (expand_normal (CALL_EXPR_ARG (exp, 0))); - return const0_rtx; - - case BUILT_IN_SAVEREGS: - return expand_builtin_saveregs (); - - case BUILT_IN_VA_ARG_PACK: - /* All valid uses of __builtin_va_arg_pack () are removed during - inlining. */ - error ("invalid use of %<__builtin_va_arg_pack ()%>"); - return const0_rtx; - - case BUILT_IN_VA_ARG_PACK_LEN: - /* All valid uses of __builtin_va_arg_pack_len () are removed during - inlining. */ - error ("invalid use of %<__builtin_va_arg_pack_len ()%>"); - return const0_rtx; - - /* Return the address of the first anonymous stack arg. */ - case BUILT_IN_NEXT_ARG: - if (fold_builtin_next_arg (exp, false)) - return const0_rtx; - return expand_builtin_next_arg (); - - case BUILT_IN_CLEAR_CACHE: - expand_builtin___clear_cache (exp); - return const0_rtx; - - case BUILT_IN_CLASSIFY_TYPE: - return expand_builtin_classify_type (exp); - - case BUILT_IN_CONSTANT_P: - return const0_rtx; - - case BUILT_IN_FRAME_ADDRESS: - case BUILT_IN_RETURN_ADDRESS: - return expand_builtin_frame_address (fndecl, exp); - - /* Returns the address of the area where the structure is returned. - 0 otherwise. */ - case BUILT_IN_AGGREGATE_INCOMING_ADDRESS: - if (call_expr_nargs (exp) != 0 - || ! AGGREGATE_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))) - || !MEM_P (DECL_RTL (DECL_RESULT (current_function_decl)))) - return const0_rtx; - else - return XEXP (DECL_RTL (DECL_RESULT (current_function_decl)), 0); - - CASE_BUILT_IN_ALLOCA: - target = expand_builtin_alloca (exp); - if (target) - return target; - break; - - case BUILT_IN_ASAN_ALLOCAS_UNPOISON: - return expand_asan_emit_allocas_unpoison (exp); - - case BUILT_IN_STACK_SAVE: - return expand_stack_save (); - - case BUILT_IN_STACK_RESTORE: - expand_stack_restore (CALL_EXPR_ARG (exp, 0)); - return const0_rtx; - - case BUILT_IN_BSWAP16: - case BUILT_IN_BSWAP32: - case BUILT_IN_BSWAP64: - case BUILT_IN_BSWAP128: - target = expand_builtin_bswap (target_mode, exp, target, subtarget); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_FFS): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, ffs_optab); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_CLZ): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, clz_optab); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_CTZ): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, ctz_optab); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_CLRSB): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, clrsb_optab); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_POPCOUNT): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, popcount_optab); - if (target) - return target; - break; - - CASE_INT_FN (BUILT_IN_PARITY): - target = expand_builtin_unop (target_mode, exp, target, - subtarget, parity_optab); - if (target) - return target; - break; - - case BUILT_IN_STRLEN: - target = expand_builtin_strlen (exp, target, target_mode); - if (target) - return target; - break; - - case BUILT_IN_STRNLEN: - target = expand_builtin_strnlen (exp, target, target_mode); - if (target) - return target; - break; - - case BUILT_IN_STRCAT: - target = expand_builtin_strcat (exp); - if (target) - return target; - break; - - case BUILT_IN_GETTEXT: - case BUILT_IN_PUTS: - case BUILT_IN_PUTS_UNLOCKED: - case BUILT_IN_STRDUP: - if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - check_read_access (exp, CALL_EXPR_ARG (exp, 0)); - break; - - case BUILT_IN_INDEX: - case BUILT_IN_RINDEX: - case BUILT_IN_STRCHR: - case BUILT_IN_STRRCHR: - if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - check_read_access (exp, CALL_EXPR_ARG (exp, 0)); - break; - - case BUILT_IN_FPUTS: - case BUILT_IN_FPUTS_UNLOCKED: - if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - check_read_access (exp, CALL_EXPR_ARG (exp, 0)); - break; - - case BUILT_IN_STRNDUP: - if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - check_read_access (exp, CALL_EXPR_ARG (exp, 0), CALL_EXPR_ARG (exp, 1)); - break; - - case BUILT_IN_STRCASECMP: - case BUILT_IN_STRPBRK: - case BUILT_IN_STRSPN: - case BUILT_IN_STRCSPN: - case BUILT_IN_STRSTR: - if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - { - check_read_access (exp, CALL_EXPR_ARG (exp, 0)); - check_read_access (exp, CALL_EXPR_ARG (exp, 1)); - } - break; - - case BUILT_IN_STRCPY: - target = expand_builtin_strcpy (exp, target); - if (target) - return target; - break; - - case BUILT_IN_STRNCAT: - target = expand_builtin_strncat (exp, target); - if (target) - return target; - break; - - case BUILT_IN_STRNCPY: - target = expand_builtin_strncpy (exp, target); - if (target) - return target; - break; - - case BUILT_IN_STPCPY: - target = expand_builtin_stpcpy (exp, target, mode); - if (target) - return target; - break; - - case BUILT_IN_STPNCPY: - target = expand_builtin_stpncpy (exp, target); - if (target) - return target; - break; - - case BUILT_IN_MEMCHR: - target = expand_builtin_memchr (exp, target); - if (target) - return target; - break; - - case BUILT_IN_MEMCPY: - target = expand_builtin_memcpy (exp, target); - if (target) - return target; - break; - - case BUILT_IN_MEMMOVE: - target = expand_builtin_memmove (exp, target); - if (target) - return target; - break; - - case BUILT_IN_MEMPCPY: - target = expand_builtin_mempcpy (exp, target); - if (target) - return target; - break; - - case BUILT_IN_MEMSET: - target = expand_builtin_memset (exp, target, mode); - if (target) - return target; - break; - - case BUILT_IN_BZERO: - target = expand_builtin_bzero (exp); - if (target) - return target; - break; - - /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it - back to a BUILT_IN_STRCMP. Remember to delete the 3rd parameter - when changing it to a strcmp call. */ - case BUILT_IN_STRCMP_EQ: - target = expand_builtin_memcmp (exp, target, true); - if (target) - return target; - - /* Change this call back to a BUILT_IN_STRCMP. */ - TREE_OPERAND (exp, 1) - = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRCMP)); - - /* Delete the last parameter. */ - unsigned int i; - vec *arg_vec; - vec_alloc (arg_vec, 2); - for (i = 0; i < 2; i++) - arg_vec->quick_push (CALL_EXPR_ARG (exp, i)); - exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), arg_vec); - /* FALLTHROUGH */ - - case BUILT_IN_STRCMP: - target = expand_builtin_strcmp (exp, target); - if (target) - return target; - break; - - /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it - back to a BUILT_IN_STRNCMP. */ - case BUILT_IN_STRNCMP_EQ: - target = expand_builtin_memcmp (exp, target, true); - if (target) - return target; - - /* Change it back to a BUILT_IN_STRNCMP. */ - TREE_OPERAND (exp, 1) - = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRNCMP)); - /* FALLTHROUGH */ - - case BUILT_IN_STRNCMP: - target = expand_builtin_strncmp (exp, target, mode); - if (target) - return target; - break; - - case BUILT_IN_BCMP: - case BUILT_IN_MEMCMP: - case BUILT_IN_MEMCMP_EQ: - target = expand_builtin_memcmp (exp, target, fcode == BUILT_IN_MEMCMP_EQ); - if (target) - return target; - if (fcode == BUILT_IN_MEMCMP_EQ) - { - tree newdecl = builtin_decl_explicit (BUILT_IN_MEMCMP); - TREE_OPERAND (exp, 1) = build_fold_addr_expr (newdecl); - } - break; - - case BUILT_IN_SETJMP: - /* This should have been lowered to the builtins below. */ - gcc_unreachable (); - - case BUILT_IN_SETJMP_SETUP: - /* __builtin_setjmp_setup is passed a pointer to an array of five words - and the receiver label. */ - if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) - { - rtx buf_addr = expand_expr (CALL_EXPR_ARG (exp, 0), subtarget, - VOIDmode, EXPAND_NORMAL); - tree label = TREE_OPERAND (CALL_EXPR_ARG (exp, 1), 0); - rtx_insn *label_r = label_rtx (label); - - /* This is copied from the handling of non-local gotos. */ - expand_builtin_setjmp_setup (buf_addr, label_r); - nonlocal_goto_handler_labels - = gen_rtx_INSN_LIST (VOIDmode, label_r, - nonlocal_goto_handler_labels); - /* ??? Do not let expand_label treat us as such since we would - not want to be both on the list of non-local labels and on - the list of forced labels. */ - FORCED_LABEL (label) = 0; - return const0_rtx; - } - break; - - case BUILT_IN_SETJMP_RECEIVER: - /* __builtin_setjmp_receiver is passed the receiver label. */ - if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - { - tree label = TREE_OPERAND (CALL_EXPR_ARG (exp, 0), 0); - rtx_insn *label_r = label_rtx (label); - - expand_builtin_setjmp_receiver (label_r); - return const0_rtx; - } - break; - - /* __builtin_longjmp is passed a pointer to an array of five words. - It's similar to the C library longjmp function but works with - __builtin_setjmp above. */ - case BUILT_IN_LONGJMP: - if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - { - rtx buf_addr = expand_expr (CALL_EXPR_ARG (exp, 0), subtarget, - VOIDmode, EXPAND_NORMAL); - rtx value = expand_normal (CALL_EXPR_ARG (exp, 1)); - - if (value != const1_rtx) - { - error ("%<__builtin_longjmp%> second argument must be 1"); - return const0_rtx; - } - - expand_builtin_longjmp (buf_addr, value); - return const0_rtx; - } - break; - - case BUILT_IN_NONLOCAL_GOTO: - target = expand_builtin_nonlocal_goto (exp); - if (target) - return target; - break; - - /* This updates the setjmp buffer that is its argument with the value - of the current stack pointer. */ - case BUILT_IN_UPDATE_SETJMP_BUF: - if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) - { - rtx buf_addr - = expand_normal (CALL_EXPR_ARG (exp, 0)); - - expand_builtin_update_setjmp_buf (buf_addr); - return const0_rtx; - } - break; - - case BUILT_IN_TRAP: - expand_builtin_trap (); - return const0_rtx; - - case BUILT_IN_UNREACHABLE: - expand_builtin_unreachable (); - return const0_rtx; - - CASE_FLT_FN (BUILT_IN_SIGNBIT): - case BUILT_IN_SIGNBITD32: - case BUILT_IN_SIGNBITD64: - case BUILT_IN_SIGNBITD128: - target = expand_builtin_signbit (exp, target); - if (target) - return target; - break; - - /* Various hooks for the DWARF 2 __throw routine. */ - case BUILT_IN_UNWIND_INIT: - expand_builtin_unwind_init (); - return const0_rtx; - case BUILT_IN_DWARF_CFA: - return virtual_cfa_rtx; -#ifdef DWARF2_UNWIND_INFO - case BUILT_IN_DWARF_SP_COLUMN: - return expand_builtin_dwarf_sp_column (); - case BUILT_IN_INIT_DWARF_REG_SIZES: - expand_builtin_init_dwarf_reg_sizes (CALL_EXPR_ARG (exp, 0)); - return const0_rtx; -#endif - case BUILT_IN_FROB_RETURN_ADDR: - return expand_builtin_frob_return_addr (CALL_EXPR_ARG (exp, 0)); - case BUILT_IN_EXTRACT_RETURN_ADDR: - return expand_builtin_extract_return_addr (CALL_EXPR_ARG (exp, 0)); - case BUILT_IN_EH_RETURN: - expand_builtin_eh_return (CALL_EXPR_ARG (exp, 0), - CALL_EXPR_ARG (exp, 1)); - return const0_rtx; - case BUILT_IN_EH_RETURN_DATA_REGNO: - return expand_builtin_eh_return_data_regno (exp); - case BUILT_IN_EXTEND_POINTER: - return expand_builtin_extend_pointer (CALL_EXPR_ARG (exp, 0)); - case BUILT_IN_EH_POINTER: - return expand_builtin_eh_pointer (exp); - case BUILT_IN_EH_FILTER: - return expand_builtin_eh_filter (exp); - case BUILT_IN_EH_COPY_VALUES: - return expand_builtin_eh_copy_values (exp); - - case BUILT_IN_VA_START: - return expand_builtin_va_start (exp); - case BUILT_IN_VA_END: - return expand_builtin_va_end (exp); - case BUILT_IN_VA_COPY: - return expand_builtin_va_copy (exp); - case BUILT_IN_EXPECT: - return expand_builtin_expect (exp, target); - case BUILT_IN_EXPECT_WITH_PROBABILITY: - return expand_builtin_expect_with_probability (exp, target); - case BUILT_IN_ASSUME_ALIGNED: - return expand_builtin_assume_aligned (exp, target); - case BUILT_IN_PREFETCH: - expand_builtin_prefetch (exp); - return const0_rtx; - - case BUILT_IN_INIT_TRAMPOLINE: - return expand_builtin_init_trampoline (exp, true); - case BUILT_IN_INIT_HEAP_TRAMPOLINE: - return expand_builtin_init_trampoline (exp, false); - case BUILT_IN_ADJUST_TRAMPOLINE: - return expand_builtin_adjust_trampoline (exp); - - case BUILT_IN_INIT_DESCRIPTOR: - return expand_builtin_init_descriptor (exp); - case BUILT_IN_ADJUST_DESCRIPTOR: - return expand_builtin_adjust_descriptor (exp); - - case BUILT_IN_FORK: - case BUILT_IN_EXECL: - case BUILT_IN_EXECV: - case BUILT_IN_EXECLP: - case BUILT_IN_EXECLE: - case BUILT_IN_EXECVP: - case BUILT_IN_EXECVE: - target = expand_builtin_fork_or_exec (fndecl, exp, target, ignore); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_ADD_1: - case BUILT_IN_SYNC_FETCH_AND_ADD_2: - case BUILT_IN_SYNC_FETCH_AND_ADD_4: - case BUILT_IN_SYNC_FETCH_AND_ADD_8: - case BUILT_IN_SYNC_FETCH_AND_ADD_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_ADD_1); - target = expand_builtin_sync_operation (mode, exp, PLUS, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_SUB_1: - case BUILT_IN_SYNC_FETCH_AND_SUB_2: - case BUILT_IN_SYNC_FETCH_AND_SUB_4: - case BUILT_IN_SYNC_FETCH_AND_SUB_8: - case BUILT_IN_SYNC_FETCH_AND_SUB_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_SUB_1); - target = expand_builtin_sync_operation (mode, exp, MINUS, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_OR_1: - case BUILT_IN_SYNC_FETCH_AND_OR_2: - case BUILT_IN_SYNC_FETCH_AND_OR_4: - case BUILT_IN_SYNC_FETCH_AND_OR_8: - case BUILT_IN_SYNC_FETCH_AND_OR_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_OR_1); - target = expand_builtin_sync_operation (mode, exp, IOR, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_AND_1: - case BUILT_IN_SYNC_FETCH_AND_AND_2: - case BUILT_IN_SYNC_FETCH_AND_AND_4: - case BUILT_IN_SYNC_FETCH_AND_AND_8: - case BUILT_IN_SYNC_FETCH_AND_AND_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_AND_1); - target = expand_builtin_sync_operation (mode, exp, AND, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_XOR_1: - case BUILT_IN_SYNC_FETCH_AND_XOR_2: - case BUILT_IN_SYNC_FETCH_AND_XOR_4: - case BUILT_IN_SYNC_FETCH_AND_XOR_8: - case BUILT_IN_SYNC_FETCH_AND_XOR_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_XOR_1); - target = expand_builtin_sync_operation (mode, exp, XOR, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_FETCH_AND_NAND_1: - case BUILT_IN_SYNC_FETCH_AND_NAND_2: - case BUILT_IN_SYNC_FETCH_AND_NAND_4: - case BUILT_IN_SYNC_FETCH_AND_NAND_8: - case BUILT_IN_SYNC_FETCH_AND_NAND_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_FETCH_AND_NAND_1); - target = expand_builtin_sync_operation (mode, exp, NOT, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_ADD_AND_FETCH_1: - case BUILT_IN_SYNC_ADD_AND_FETCH_2: - case BUILT_IN_SYNC_ADD_AND_FETCH_4: - case BUILT_IN_SYNC_ADD_AND_FETCH_8: - case BUILT_IN_SYNC_ADD_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_ADD_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, PLUS, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_SUB_AND_FETCH_1: - case BUILT_IN_SYNC_SUB_AND_FETCH_2: - case BUILT_IN_SYNC_SUB_AND_FETCH_4: - case BUILT_IN_SYNC_SUB_AND_FETCH_8: - case BUILT_IN_SYNC_SUB_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_SUB_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, MINUS, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_OR_AND_FETCH_1: - case BUILT_IN_SYNC_OR_AND_FETCH_2: - case BUILT_IN_SYNC_OR_AND_FETCH_4: - case BUILT_IN_SYNC_OR_AND_FETCH_8: - case BUILT_IN_SYNC_OR_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_OR_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, IOR, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_AND_AND_FETCH_1: - case BUILT_IN_SYNC_AND_AND_FETCH_2: - case BUILT_IN_SYNC_AND_AND_FETCH_4: - case BUILT_IN_SYNC_AND_AND_FETCH_8: - case BUILT_IN_SYNC_AND_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_AND_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, AND, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_XOR_AND_FETCH_1: - case BUILT_IN_SYNC_XOR_AND_FETCH_2: - case BUILT_IN_SYNC_XOR_AND_FETCH_4: - case BUILT_IN_SYNC_XOR_AND_FETCH_8: - case BUILT_IN_SYNC_XOR_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_XOR_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, XOR, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_NAND_AND_FETCH_1: - case BUILT_IN_SYNC_NAND_AND_FETCH_2: - case BUILT_IN_SYNC_NAND_AND_FETCH_4: - case BUILT_IN_SYNC_NAND_AND_FETCH_8: - case BUILT_IN_SYNC_NAND_AND_FETCH_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_NAND_AND_FETCH_1); - target = expand_builtin_sync_operation (mode, exp, NOT, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_1: - case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_2: - case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_4: - case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_8: - case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_16: - if (mode == VOIDmode) - mode = TYPE_MODE (boolean_type_node); - if (!target || !register_operand (target, mode)) - target = gen_reg_rtx (mode); - - mode = get_builtin_sync_mode - (fcode - BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_1); - target = expand_builtin_compare_and_swap (mode, exp, true, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1: - case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_2: - case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_4: - case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_8: - case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_16: - mode = get_builtin_sync_mode - (fcode - BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1); - target = expand_builtin_compare_and_swap (mode, exp, false, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_LOCK_TEST_AND_SET_1: - case BUILT_IN_SYNC_LOCK_TEST_AND_SET_2: - case BUILT_IN_SYNC_LOCK_TEST_AND_SET_4: - case BUILT_IN_SYNC_LOCK_TEST_AND_SET_8: - case BUILT_IN_SYNC_LOCK_TEST_AND_SET_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_LOCK_TEST_AND_SET_1); - target = expand_builtin_sync_lock_test_and_set (mode, exp, target); - if (target) - return target; - break; - - case BUILT_IN_SYNC_LOCK_RELEASE_1: - case BUILT_IN_SYNC_LOCK_RELEASE_2: - case BUILT_IN_SYNC_LOCK_RELEASE_4: - case BUILT_IN_SYNC_LOCK_RELEASE_8: - case BUILT_IN_SYNC_LOCK_RELEASE_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SYNC_LOCK_RELEASE_1); - expand_builtin_sync_lock_release (mode, exp); - return const0_rtx; - - case BUILT_IN_SYNC_SYNCHRONIZE: - expand_builtin_sync_synchronize (); - return const0_rtx; - - case BUILT_IN_ATOMIC_EXCHANGE_1: - case BUILT_IN_ATOMIC_EXCHANGE_2: - case BUILT_IN_ATOMIC_EXCHANGE_4: - case BUILT_IN_ATOMIC_EXCHANGE_8: - case BUILT_IN_ATOMIC_EXCHANGE_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_EXCHANGE_1); - target = expand_builtin_atomic_exchange (mode, exp, target); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1: - case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_2: - case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_4: - case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_8: - case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_16: - { - unsigned int nargs, z; - vec *vec; - - mode = - get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_COMPARE_EXCHANGE_1); - target = expand_builtin_atomic_compare_exchange (mode, exp, target); - if (target) - return target; - - /* If this is turned into an external library call, the weak parameter - must be dropped to match the expected parameter list. */ - nargs = call_expr_nargs (exp); - vec_alloc (vec, nargs - 1); - for (z = 0; z < 3; z++) - vec->quick_push (CALL_EXPR_ARG (exp, z)); - /* Skip the boolean weak parameter. */ - for (z = 4; z < 6; z++) - vec->quick_push (CALL_EXPR_ARG (exp, z)); - exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), vec); - break; - } - - case BUILT_IN_ATOMIC_LOAD_1: - case BUILT_IN_ATOMIC_LOAD_2: - case BUILT_IN_ATOMIC_LOAD_4: - case BUILT_IN_ATOMIC_LOAD_8: - case BUILT_IN_ATOMIC_LOAD_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_LOAD_1); - target = expand_builtin_atomic_load (mode, exp, target); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_STORE_1: - case BUILT_IN_ATOMIC_STORE_2: - case BUILT_IN_ATOMIC_STORE_4: - case BUILT_IN_ATOMIC_STORE_8: - case BUILT_IN_ATOMIC_STORE_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_STORE_1); - target = expand_builtin_atomic_store (mode, exp); - if (target) - return const0_rtx; - break; - - case BUILT_IN_ATOMIC_ADD_FETCH_1: - case BUILT_IN_ATOMIC_ADD_FETCH_2: - case BUILT_IN_ATOMIC_ADD_FETCH_4: - case BUILT_IN_ATOMIC_ADD_FETCH_8: - case BUILT_IN_ATOMIC_ADD_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_ADD_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_ADD_1 + - (fcode - BUILT_IN_ATOMIC_ADD_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, PLUS, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_SUB_FETCH_1: - case BUILT_IN_ATOMIC_SUB_FETCH_2: - case BUILT_IN_ATOMIC_SUB_FETCH_4: - case BUILT_IN_ATOMIC_SUB_FETCH_8: - case BUILT_IN_ATOMIC_SUB_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_SUB_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_SUB_1 + - (fcode - BUILT_IN_ATOMIC_SUB_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, MINUS, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_AND_FETCH_1: - case BUILT_IN_ATOMIC_AND_FETCH_2: - case BUILT_IN_ATOMIC_AND_FETCH_4: - case BUILT_IN_ATOMIC_AND_FETCH_8: - case BUILT_IN_ATOMIC_AND_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_AND_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_AND_1 + - (fcode - BUILT_IN_ATOMIC_AND_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, AND, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_NAND_FETCH_1: - case BUILT_IN_ATOMIC_NAND_FETCH_2: - case BUILT_IN_ATOMIC_NAND_FETCH_4: - case BUILT_IN_ATOMIC_NAND_FETCH_8: - case BUILT_IN_ATOMIC_NAND_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_NAND_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_NAND_1 + - (fcode - BUILT_IN_ATOMIC_NAND_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, NOT, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_XOR_FETCH_1: - case BUILT_IN_ATOMIC_XOR_FETCH_2: - case BUILT_IN_ATOMIC_XOR_FETCH_4: - case BUILT_IN_ATOMIC_XOR_FETCH_8: - case BUILT_IN_ATOMIC_XOR_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_XOR_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_XOR_1 + - (fcode - BUILT_IN_ATOMIC_XOR_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, XOR, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_OR_FETCH_1: - case BUILT_IN_ATOMIC_OR_FETCH_2: - case BUILT_IN_ATOMIC_OR_FETCH_4: - case BUILT_IN_ATOMIC_OR_FETCH_8: - case BUILT_IN_ATOMIC_OR_FETCH_16: - { - enum built_in_function lib; - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_OR_FETCH_1); - lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_OR_1 + - (fcode - BUILT_IN_ATOMIC_OR_FETCH_1)); - target = expand_builtin_atomic_fetch_op (mode, exp, target, IOR, true, - ignore, lib); - if (target) - return target; - break; - } - case BUILT_IN_ATOMIC_FETCH_ADD_1: - case BUILT_IN_ATOMIC_FETCH_ADD_2: - case BUILT_IN_ATOMIC_FETCH_ADD_4: - case BUILT_IN_ATOMIC_FETCH_ADD_8: - case BUILT_IN_ATOMIC_FETCH_ADD_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_ADD_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, PLUS, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_FETCH_SUB_1: - case BUILT_IN_ATOMIC_FETCH_SUB_2: - case BUILT_IN_ATOMIC_FETCH_SUB_4: - case BUILT_IN_ATOMIC_FETCH_SUB_8: - case BUILT_IN_ATOMIC_FETCH_SUB_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_SUB_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, MINUS, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_FETCH_AND_1: - case BUILT_IN_ATOMIC_FETCH_AND_2: - case BUILT_IN_ATOMIC_FETCH_AND_4: - case BUILT_IN_ATOMIC_FETCH_AND_8: - case BUILT_IN_ATOMIC_FETCH_AND_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_AND_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, AND, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_FETCH_NAND_1: - case BUILT_IN_ATOMIC_FETCH_NAND_2: - case BUILT_IN_ATOMIC_FETCH_NAND_4: - case BUILT_IN_ATOMIC_FETCH_NAND_8: - case BUILT_IN_ATOMIC_FETCH_NAND_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_NAND_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, NOT, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_FETCH_XOR_1: - case BUILT_IN_ATOMIC_FETCH_XOR_2: - case BUILT_IN_ATOMIC_FETCH_XOR_4: - case BUILT_IN_ATOMIC_FETCH_XOR_8: - case BUILT_IN_ATOMIC_FETCH_XOR_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_XOR_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, XOR, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_FETCH_OR_1: - case BUILT_IN_ATOMIC_FETCH_OR_2: - case BUILT_IN_ATOMIC_FETCH_OR_4: - case BUILT_IN_ATOMIC_FETCH_OR_8: - case BUILT_IN_ATOMIC_FETCH_OR_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_OR_1); - target = expand_builtin_atomic_fetch_op (mode, exp, target, IOR, false, - ignore, BUILT_IN_NONE); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_TEST_AND_SET: - return expand_builtin_atomic_test_and_set (exp, target); - - case BUILT_IN_ATOMIC_CLEAR: - return expand_builtin_atomic_clear (exp); - - case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: - return expand_builtin_atomic_always_lock_free (exp); - - case BUILT_IN_ATOMIC_IS_LOCK_FREE: - target = expand_builtin_atomic_is_lock_free (exp); - if (target) - return target; - break; - - case BUILT_IN_ATOMIC_THREAD_FENCE: - expand_builtin_atomic_thread_fence (exp); - return const0_rtx; - - case BUILT_IN_ATOMIC_SIGNAL_FENCE: - expand_builtin_atomic_signal_fence (exp); - return const0_rtx; - - case BUILT_IN_OBJECT_SIZE: - return expand_builtin_object_size (exp); - - case BUILT_IN_MEMCPY_CHK: - case BUILT_IN_MEMPCPY_CHK: - case BUILT_IN_MEMMOVE_CHK: - case BUILT_IN_MEMSET_CHK: - target = expand_builtin_memory_chk (exp, target, mode, fcode); - if (target) - return target; - break; - - case BUILT_IN_STRCPY_CHK: - case BUILT_IN_STPCPY_CHK: - case BUILT_IN_STRNCPY_CHK: - case BUILT_IN_STPNCPY_CHK: - case BUILT_IN_STRCAT_CHK: - case BUILT_IN_STRNCAT_CHK: - case BUILT_IN_SNPRINTF_CHK: - case BUILT_IN_VSNPRINTF_CHK: - maybe_emit_chk_warning (exp, fcode); - break; - - case BUILT_IN_SPRINTF_CHK: - case BUILT_IN_VSPRINTF_CHK: - maybe_emit_sprintf_chk_warning (exp, fcode); - break; - - case BUILT_IN_THREAD_POINTER: - return expand_builtin_thread_pointer (exp, target); - - case BUILT_IN_SET_THREAD_POINTER: - expand_builtin_set_thread_pointer (exp); - return const0_rtx; - - case BUILT_IN_ACC_ON_DEVICE: - /* Do library call, if we failed to expand the builtin when - folding. */ - break; - - case BUILT_IN_GOACC_PARLEVEL_ID: - case BUILT_IN_GOACC_PARLEVEL_SIZE: - return expand_builtin_goacc_parlevel_id_size (exp, target, ignore); - - case BUILT_IN_SPECULATION_SAFE_VALUE_PTR: - return expand_speculation_safe_value (VOIDmode, exp, target, ignore); - - case BUILT_IN_SPECULATION_SAFE_VALUE_1: - case BUILT_IN_SPECULATION_SAFE_VALUE_2: - case BUILT_IN_SPECULATION_SAFE_VALUE_4: - case BUILT_IN_SPECULATION_SAFE_VALUE_8: - case BUILT_IN_SPECULATION_SAFE_VALUE_16: - mode = get_builtin_sync_mode (fcode - BUILT_IN_SPECULATION_SAFE_VALUE_1); - return expand_speculation_safe_value (mode, exp, target, ignore); - - default: /* just do library call, if unknown builtin */ - break; - } - - /* The switch statement above can drop through to cause the function - to be called normally. */ - return expand_call (exp, target, ignore); -} - -/* Determine whether a tree node represents a call to a built-in - function. If the tree T is a call to a built-in function with - the right number of arguments of the appropriate types, return - the DECL_FUNCTION_CODE of the call, e.g. BUILT_IN_SQRT. - Otherwise the return value is END_BUILTINS. */ - -enum built_in_function -builtin_mathfn_code (const_tree t) -{ - const_tree fndecl, arg, parmlist; - const_tree argtype, parmtype; - const_call_expr_arg_iterator iter; - - if (TREE_CODE (t) != CALL_EXPR) - return END_BUILTINS; - - fndecl = get_callee_fndecl (t); - if (fndecl == NULL_TREE || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) - return END_BUILTINS; - - parmlist = TYPE_ARG_TYPES (TREE_TYPE (fndecl)); - init_const_call_expr_arg_iterator (t, &iter); - for (; parmlist; parmlist = TREE_CHAIN (parmlist)) - { - /* If a function doesn't take a variable number of arguments, - the last element in the list will have type `void'. */ - parmtype = TREE_VALUE (parmlist); - if (VOID_TYPE_P (parmtype)) - { - if (more_const_call_expr_args_p (&iter)) - return END_BUILTINS; - return DECL_FUNCTION_CODE (fndecl); - } - - if (! more_const_call_expr_args_p (&iter)) - return END_BUILTINS; - - arg = next_const_call_expr_arg (&iter); - argtype = TREE_TYPE (arg); - - if (SCALAR_FLOAT_TYPE_P (parmtype)) - { - if (! SCALAR_FLOAT_TYPE_P (argtype)) - return END_BUILTINS; - } - else if (COMPLEX_FLOAT_TYPE_P (parmtype)) - { - if (! COMPLEX_FLOAT_TYPE_P (argtype)) - return END_BUILTINS; - } - else if (POINTER_TYPE_P (parmtype)) - { - if (! POINTER_TYPE_P (argtype)) - return END_BUILTINS; - } - else if (INTEGRAL_TYPE_P (parmtype)) - { - if (! INTEGRAL_TYPE_P (argtype)) - return END_BUILTINS; - } - else - return END_BUILTINS; - } - - /* Variable-length argument list. */ - return DECL_FUNCTION_CODE (fndecl); -} - -/* Fold a call to __builtin_constant_p, if we know its argument ARG will - evaluate to a constant. */ - -static tree -fold_builtin_constant_p (tree arg) -{ - /* We return 1 for a numeric type that's known to be a constant - value at compile-time or for an aggregate type that's a - literal constant. */ - STRIP_NOPS (arg); - - /* If we know this is a constant, emit the constant of one. */ - if (CONSTANT_CLASS_P (arg) - || (TREE_CODE (arg) == CONSTRUCTOR - && TREE_CONSTANT (arg))) - return integer_one_node; - if (TREE_CODE (arg) == ADDR_EXPR) - { - tree op = TREE_OPERAND (arg, 0); - if (TREE_CODE (op) == STRING_CST - || (TREE_CODE (op) == ARRAY_REF - && integer_zerop (TREE_OPERAND (op, 1)) - && TREE_CODE (TREE_OPERAND (op, 0)) == STRING_CST)) - return integer_one_node; - } - - /* If this expression has side effects, show we don't know it to be a - constant. Likewise if it's a pointer or aggregate type since in - those case we only want literals, since those are only optimized - when generating RTL, not later. - And finally, if we are compiling an initializer, not code, we - need to return a definite result now; there's not going to be any - more optimization done. */ - if (TREE_SIDE_EFFECTS (arg) - || AGGREGATE_TYPE_P (TREE_TYPE (arg)) - || POINTER_TYPE_P (TREE_TYPE (arg)) - || cfun == 0 - || folding_initializer - || force_folding_builtin_constant_p) - return integer_zero_node; - - return NULL_TREE; -} - -/* Create builtin_expect or builtin_expect_with_probability - with PRED and EXPECTED as its arguments and return it as a truthvalue. - Fortran FE can also produce builtin_expect with PREDICTOR as third argument. - builtin_expect_with_probability instead uses third argument as PROBABILITY - value. */ - -static tree -build_builtin_expect_predicate (location_t loc, tree pred, tree expected, - tree predictor, tree probability) -{ - tree fn, arg_types, pred_type, expected_type, call_expr, ret_type; - - fn = builtin_decl_explicit (probability == NULL_TREE ? BUILT_IN_EXPECT - : BUILT_IN_EXPECT_WITH_PROBABILITY); - arg_types = TYPE_ARG_TYPES (TREE_TYPE (fn)); - ret_type = TREE_TYPE (TREE_TYPE (fn)); - pred_type = TREE_VALUE (arg_types); - expected_type = TREE_VALUE (TREE_CHAIN (arg_types)); - - pred = fold_convert_loc (loc, pred_type, pred); - expected = fold_convert_loc (loc, expected_type, expected); - - if (probability) - call_expr = build_call_expr_loc (loc, fn, 3, pred, expected, probability); - else - call_expr = build_call_expr_loc (loc, fn, predictor ? 3 : 2, pred, expected, - predictor); - - return build2 (NE_EXPR, TREE_TYPE (pred), call_expr, - build_int_cst (ret_type, 0)); -} - -/* Fold a call to builtin_expect with arguments ARG0, ARG1, ARG2, ARG3. Return - NULL_TREE if no simplification is possible. */ - -tree -fold_builtin_expect (location_t loc, tree arg0, tree arg1, tree arg2, - tree arg3) -{ - tree inner, fndecl, inner_arg0; - enum tree_code code; - - /* Distribute the expected value over short-circuiting operators. - See through the cast from truthvalue_type_node to long. */ - inner_arg0 = arg0; - while (CONVERT_EXPR_P (inner_arg0) - && INTEGRAL_TYPE_P (TREE_TYPE (inner_arg0)) - && INTEGRAL_TYPE_P (TREE_TYPE (TREE_OPERAND (inner_arg0, 0)))) - inner_arg0 = TREE_OPERAND (inner_arg0, 0); - - /* If this is a builtin_expect within a builtin_expect keep the - inner one. See through a comparison against a constant. It - might have been added to create a thruthvalue. */ - inner = inner_arg0; - - if (COMPARISON_CLASS_P (inner) - && TREE_CODE (TREE_OPERAND (inner, 1)) == INTEGER_CST) - inner = TREE_OPERAND (inner, 0); - - if (TREE_CODE (inner) == CALL_EXPR - && (fndecl = get_callee_fndecl (inner)) - && (fndecl_built_in_p (fndecl, BUILT_IN_EXPECT) - || fndecl_built_in_p (fndecl, BUILT_IN_EXPECT_WITH_PROBABILITY))) - return arg0; - - inner = inner_arg0; - code = TREE_CODE (inner); - if (code == TRUTH_ANDIF_EXPR || code == TRUTH_ORIF_EXPR) - { - tree op0 = TREE_OPERAND (inner, 0); - tree op1 = TREE_OPERAND (inner, 1); - arg1 = save_expr (arg1); - - op0 = build_builtin_expect_predicate (loc, op0, arg1, arg2, arg3); - op1 = build_builtin_expect_predicate (loc, op1, arg1, arg2, arg3); - inner = build2 (code, TREE_TYPE (inner), op0, op1); - - return fold_convert_loc (loc, TREE_TYPE (arg0), inner); - } - - /* If the argument isn't invariant then there's nothing else we can do. */ - if (!TREE_CONSTANT (inner_arg0)) - return NULL_TREE; - - /* If we expect that a comparison against the argument will fold to - a constant return the constant. In practice, this means a true - constant or the address of a non-weak symbol. */ - inner = inner_arg0; - STRIP_NOPS (inner); - if (TREE_CODE (inner) == ADDR_EXPR) - { - do - { - inner = TREE_OPERAND (inner, 0); - } - while (TREE_CODE (inner) == COMPONENT_REF - || TREE_CODE (inner) == ARRAY_REF); - if (VAR_OR_FUNCTION_DECL_P (inner) && DECL_WEAK (inner)) - return NULL_TREE; - } - - /* Otherwise, ARG0 already has the proper type for the return value. */ - return arg0; -} - -/* Fold a call to __builtin_classify_type with argument ARG. */ - -static tree -fold_builtin_classify_type (tree arg) -{ - if (arg == 0) - return build_int_cst (integer_type_node, no_type_class); - - return build_int_cst (integer_type_node, type_to_class (TREE_TYPE (arg))); -} - -/* Fold a call EXPR (which may be null) to __builtin_strlen with argument - ARG. */ - -static tree -fold_builtin_strlen (location_t loc, tree expr, tree type, tree arg) -{ - if (!validate_arg (arg, POINTER_TYPE)) - return NULL_TREE; - else - { - c_strlen_data lendata = { }; - tree len = c_strlen (arg, 0, &lendata); - - if (len) - return fold_convert_loc (loc, type, len); - - if (!lendata.decl) - c_strlen (arg, 1, &lendata); - - if (lendata.decl) - { - if (EXPR_HAS_LOCATION (arg)) - loc = EXPR_LOCATION (arg); - else if (loc == UNKNOWN_LOCATION) - loc = input_location; - warn_string_no_nul (loc, expr, "strlen", arg, lendata.decl); - } - - return NULL_TREE; - } -} - -/* Fold a call to __builtin_inf or __builtin_huge_val. */ - -static tree -fold_builtin_inf (location_t loc, tree type, int warn) -{ - REAL_VALUE_TYPE real; - - /* __builtin_inff is intended to be usable to define INFINITY on all - targets. If an infinity is not available, INFINITY expands "to a - positive constant of type float that overflows at translation - time", footnote "In this case, using INFINITY will violate the - constraint in 6.4.4 and thus require a diagnostic." (C99 7.12#4). - Thus we pedwarn to ensure this constraint violation is - diagnosed. */ - if (!MODE_HAS_INFINITIES (TYPE_MODE (type)) && warn) - pedwarn (loc, 0, "target format does not support infinity"); - - real_inf (&real); - return build_real (type, real); -} - -/* Fold function call to builtin sincos, sincosf, or sincosl. Return - NULL_TREE if no simplification can be made. */ - -static tree -fold_builtin_sincos (location_t loc, - tree arg0, tree arg1, tree arg2) -{ - tree type; - tree fndecl, call = NULL_TREE; - - if (!validate_arg (arg0, REAL_TYPE) - || !validate_arg (arg1, POINTER_TYPE) - || !validate_arg (arg2, POINTER_TYPE)) - return NULL_TREE; - - type = TREE_TYPE (arg0); - - /* Calculate the result when the argument is a constant. */ - built_in_function fn = mathfn_built_in_2 (type, CFN_BUILT_IN_CEXPI); - if (fn == END_BUILTINS) - return NULL_TREE; - - /* Canonicalize sincos to cexpi. */ - if (TREE_CODE (arg0) == REAL_CST) - { - tree complex_type = build_complex_type (type); - call = fold_const_call (as_combined_fn (fn), complex_type, arg0); - } - if (!call) - { - if (!targetm.libc_has_function (function_c99_math_complex, type) - || !builtin_decl_implicit_p (fn)) - return NULL_TREE; - fndecl = builtin_decl_explicit (fn); - call = build_call_expr_loc (loc, fndecl, 1, arg0); - call = builtin_save_expr (call); - } - - tree ptype = build_pointer_type (type); - arg1 = fold_convert (ptype, arg1); - arg2 = fold_convert (ptype, arg2); - return build2 (COMPOUND_EXPR, void_type_node, - build2 (MODIFY_EXPR, void_type_node, - build_fold_indirect_ref_loc (loc, arg1), - fold_build1_loc (loc, IMAGPART_EXPR, type, call)), - build2 (MODIFY_EXPR, void_type_node, - build_fold_indirect_ref_loc (loc, arg2), - fold_build1_loc (loc, REALPART_EXPR, type, call))); -} - -/* Fold function call to builtin memcmp with arguments ARG1 and ARG2. - Return NULL_TREE if no simplification can be made. */ - -static tree -fold_builtin_memcmp (location_t loc, tree arg1, tree arg2, tree len) -{ - if (!validate_arg (arg1, POINTER_TYPE) - || !validate_arg (arg2, POINTER_TYPE) - || !validate_arg (len, INTEGER_TYPE)) - return NULL_TREE; - - /* If the LEN parameter is zero, return zero. */ - if (integer_zerop (len)) - return omit_two_operands_loc (loc, integer_type_node, integer_zero_node, - arg1, arg2); - - /* If ARG1 and ARG2 are the same (and not volatile), return zero. */ - if (operand_equal_p (arg1, arg2, 0)) - return omit_one_operand_loc (loc, integer_type_node, integer_zero_node, len); - - /* If len parameter is one, return an expression corresponding to - (*(const unsigned char*)arg1 - (const unsigned char*)arg2). */ - if (tree_fits_uhwi_p (len) && tree_to_uhwi (len) == 1) - { - tree cst_uchar_node = build_type_variant (unsigned_char_type_node, 1, 0); - tree cst_uchar_ptr_node - = build_pointer_type_for_mode (cst_uchar_node, ptr_mode, true); - - tree ind1 - = fold_convert_loc (loc, integer_type_node, - build1 (INDIRECT_REF, cst_uchar_node, - fold_convert_loc (loc, - cst_uchar_ptr_node, - arg1))); - tree ind2 - = fold_convert_loc (loc, integer_type_node, - build1 (INDIRECT_REF, cst_uchar_node, - fold_convert_loc (loc, - cst_uchar_ptr_node, - arg2))); - return fold_build2_loc (loc, MINUS_EXPR, integer_type_node, ind1, ind2); - } - - return NULL_TREE; -} - -/* Fold a call to builtin isascii with argument ARG. */ - -static tree -fold_builtin_isascii (location_t loc, tree arg) -{ - if (!validate_arg (arg, INTEGER_TYPE)) - return NULL_TREE; - else - { - /* Transform isascii(c) -> ((c & ~0x7f) == 0). */ - arg = fold_build2 (BIT_AND_EXPR, integer_type_node, arg, - build_int_cst (integer_type_node, - ~ (unsigned HOST_WIDE_INT) 0x7f)); - return fold_build2_loc (loc, EQ_EXPR, integer_type_node, - arg, integer_zero_node); - } -} - -/* Fold a call to builtin toascii with argument ARG. */ - -static tree -fold_builtin_toascii (location_t loc, tree arg) -{ - if (!validate_arg (arg, INTEGER_TYPE)) - return NULL_TREE; - - /* Transform toascii(c) -> (c & 0x7f). */ - return fold_build2_loc (loc, BIT_AND_EXPR, integer_type_node, arg, - build_int_cst (integer_type_node, 0x7f)); -} - -/* Fold a call to builtin isdigit with argument ARG. */ - -static tree -fold_builtin_isdigit (location_t loc, tree arg) -{ - if (!validate_arg (arg, INTEGER_TYPE)) - return NULL_TREE; - else - { - /* Transform isdigit(c) -> (unsigned)(c) - '0' <= 9. */ - /* According to the C standard, isdigit is unaffected by locale. - However, it definitely is affected by the target character set. */ - unsigned HOST_WIDE_INT target_digit0 - = lang_hooks.to_target_charset ('0'); - - if (target_digit0 == 0) - return NULL_TREE; - - arg = fold_convert_loc (loc, unsigned_type_node, arg); - arg = fold_build2 (MINUS_EXPR, unsigned_type_node, arg, - build_int_cst (unsigned_type_node, target_digit0)); - return fold_build2_loc (loc, LE_EXPR, integer_type_node, arg, - build_int_cst (unsigned_type_node, 9)); - } -} - -/* Fold a call to fabs, fabsf or fabsl with argument ARG. */ - -static tree -fold_builtin_fabs (location_t loc, tree arg, tree type) -{ - if (!validate_arg (arg, REAL_TYPE)) - return NULL_TREE; - - arg = fold_convert_loc (loc, type, arg); - return fold_build1_loc (loc, ABS_EXPR, type, arg); -} - -/* Fold a call to abs, labs, llabs or imaxabs with argument ARG. */ - -static tree -fold_builtin_abs (location_t loc, tree arg, tree type) -{ - if (!validate_arg (arg, INTEGER_TYPE)) - return NULL_TREE; - - arg = fold_convert_loc (loc, type, arg); - return fold_build1_loc (loc, ABS_EXPR, type, arg); -} - -/* Fold a call to builtin carg(a+bi) -> atan2(b,a). */ - -static tree -fold_builtin_carg (location_t loc, tree arg, tree type) -{ - if (validate_arg (arg, COMPLEX_TYPE) - && TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == REAL_TYPE) - { - tree atan2_fn = mathfn_built_in (type, BUILT_IN_ATAN2); - - if (atan2_fn) - { - tree new_arg = builtin_save_expr (arg); - tree r_arg = fold_build1_loc (loc, REALPART_EXPR, type, new_arg); - tree i_arg = fold_build1_loc (loc, IMAGPART_EXPR, type, new_arg); - return build_call_expr_loc (loc, atan2_fn, 2, i_arg, r_arg); - } - } - - return NULL_TREE; -} - -/* Fold a call to builtin frexp, we can assume the base is 2. */ - -static tree -fold_builtin_frexp (location_t loc, tree arg0, tree arg1, tree rettype) -{ - if (! validate_arg (arg0, REAL_TYPE) || ! validate_arg (arg1, POINTER_TYPE)) - return NULL_TREE; - - STRIP_NOPS (arg0); - - if (!(TREE_CODE (arg0) == REAL_CST && ! TREE_OVERFLOW (arg0))) - return NULL_TREE; - - arg1 = build_fold_indirect_ref_loc (loc, arg1); - - /* Proceed if a valid pointer type was passed in. */ - if (TYPE_MAIN_VARIANT (TREE_TYPE (arg1)) == integer_type_node) - { - const REAL_VALUE_TYPE *const value = TREE_REAL_CST_PTR (arg0); - tree frac, exp; - - switch (value->cl) + case BUILT_IN_ATOMIC_ADD_FETCH_1: + case BUILT_IN_ATOMIC_ADD_FETCH_2: + case BUILT_IN_ATOMIC_ADD_FETCH_4: + case BUILT_IN_ATOMIC_ADD_FETCH_8: + case BUILT_IN_ATOMIC_ADD_FETCH_16: + { + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_ADD_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_ADD_1 + + (fcode - BUILT_IN_ATOMIC_ADD_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, PLUS, true, + ignore, lib); + if (target) + return target; + break; + } + case BUILT_IN_ATOMIC_SUB_FETCH_1: + case BUILT_IN_ATOMIC_SUB_FETCH_2: + case BUILT_IN_ATOMIC_SUB_FETCH_4: + case BUILT_IN_ATOMIC_SUB_FETCH_8: + case BUILT_IN_ATOMIC_SUB_FETCH_16: { - case rvc_zero: - /* For +-0, return (*exp = 0, +-0). */ - exp = integer_zero_node; - frac = arg0; + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_SUB_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_SUB_1 + + (fcode - BUILT_IN_ATOMIC_SUB_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, MINUS, true, + ignore, lib); + if (target) + return target; break; - case rvc_nan: - case rvc_inf: - /* For +-NaN or +-Inf, *exp is unspecified, return arg0. */ - return omit_one_operand_loc (loc, rettype, arg0, arg1); - case rvc_normal: - { - /* Since the frexp function always expects base 2, and in - GCC normalized significands are already in the range - [0.5, 1.0), we have exactly what frexp wants. */ - REAL_VALUE_TYPE frac_rvt = *value; - SET_REAL_EXP (&frac_rvt, 0); - frac = build_real (rettype, frac_rvt); - exp = build_int_cst (integer_type_node, REAL_EXP (value)); - } + } + case BUILT_IN_ATOMIC_AND_FETCH_1: + case BUILT_IN_ATOMIC_AND_FETCH_2: + case BUILT_IN_ATOMIC_AND_FETCH_4: + case BUILT_IN_ATOMIC_AND_FETCH_8: + case BUILT_IN_ATOMIC_AND_FETCH_16: + { + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_AND_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_AND_1 + + (fcode - BUILT_IN_ATOMIC_AND_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, AND, true, + ignore, lib); + if (target) + return target; + break; + } + case BUILT_IN_ATOMIC_NAND_FETCH_1: + case BUILT_IN_ATOMIC_NAND_FETCH_2: + case BUILT_IN_ATOMIC_NAND_FETCH_4: + case BUILT_IN_ATOMIC_NAND_FETCH_8: + case BUILT_IN_ATOMIC_NAND_FETCH_16: + { + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_NAND_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_NAND_1 + + (fcode - BUILT_IN_ATOMIC_NAND_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, NOT, true, + ignore, lib); + if (target) + return target; + break; + } + case BUILT_IN_ATOMIC_XOR_FETCH_1: + case BUILT_IN_ATOMIC_XOR_FETCH_2: + case BUILT_IN_ATOMIC_XOR_FETCH_4: + case BUILT_IN_ATOMIC_XOR_FETCH_8: + case BUILT_IN_ATOMIC_XOR_FETCH_16: + { + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_XOR_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_XOR_1 + + (fcode - BUILT_IN_ATOMIC_XOR_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, XOR, true, + ignore, lib); + if (target) + return target; + break; + } + case BUILT_IN_ATOMIC_OR_FETCH_1: + case BUILT_IN_ATOMIC_OR_FETCH_2: + case BUILT_IN_ATOMIC_OR_FETCH_4: + case BUILT_IN_ATOMIC_OR_FETCH_8: + case BUILT_IN_ATOMIC_OR_FETCH_16: + { + enum built_in_function lib; + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_OR_FETCH_1); + lib = (enum built_in_function)((int)BUILT_IN_ATOMIC_FETCH_OR_1 + + (fcode - BUILT_IN_ATOMIC_OR_FETCH_1)); + target = expand_builtin_atomic_fetch_op (mode, exp, target, IOR, true, + ignore, lib); + if (target) + return target; break; - default: - gcc_unreachable (); } + case BUILT_IN_ATOMIC_FETCH_ADD_1: + case BUILT_IN_ATOMIC_FETCH_ADD_2: + case BUILT_IN_ATOMIC_FETCH_ADD_4: + case BUILT_IN_ATOMIC_FETCH_ADD_8: + case BUILT_IN_ATOMIC_FETCH_ADD_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_ADD_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, PLUS, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; + + case BUILT_IN_ATOMIC_FETCH_SUB_1: + case BUILT_IN_ATOMIC_FETCH_SUB_2: + case BUILT_IN_ATOMIC_FETCH_SUB_4: + case BUILT_IN_ATOMIC_FETCH_SUB_8: + case BUILT_IN_ATOMIC_FETCH_SUB_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_SUB_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, MINUS, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; - /* Create the COMPOUND_EXPR (*arg1 = trunc, frac). */ - arg1 = fold_build2_loc (loc, MODIFY_EXPR, rettype, arg1, exp); - TREE_SIDE_EFFECTS (arg1) = 1; - return fold_build2_loc (loc, COMPOUND_EXPR, rettype, arg1, frac); - } + case BUILT_IN_ATOMIC_FETCH_AND_1: + case BUILT_IN_ATOMIC_FETCH_AND_2: + case BUILT_IN_ATOMIC_FETCH_AND_4: + case BUILT_IN_ATOMIC_FETCH_AND_8: + case BUILT_IN_ATOMIC_FETCH_AND_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_AND_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, AND, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; + + case BUILT_IN_ATOMIC_FETCH_NAND_1: + case BUILT_IN_ATOMIC_FETCH_NAND_2: + case BUILT_IN_ATOMIC_FETCH_NAND_4: + case BUILT_IN_ATOMIC_FETCH_NAND_8: + case BUILT_IN_ATOMIC_FETCH_NAND_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_NAND_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, NOT, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; + + case BUILT_IN_ATOMIC_FETCH_XOR_1: + case BUILT_IN_ATOMIC_FETCH_XOR_2: + case BUILT_IN_ATOMIC_FETCH_XOR_4: + case BUILT_IN_ATOMIC_FETCH_XOR_8: + case BUILT_IN_ATOMIC_FETCH_XOR_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_XOR_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, XOR, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; + + case BUILT_IN_ATOMIC_FETCH_OR_1: + case BUILT_IN_ATOMIC_FETCH_OR_2: + case BUILT_IN_ATOMIC_FETCH_OR_4: + case BUILT_IN_ATOMIC_FETCH_OR_8: + case BUILT_IN_ATOMIC_FETCH_OR_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_ATOMIC_FETCH_OR_1); + target = expand_builtin_atomic_fetch_op (mode, exp, target, IOR, false, + ignore, BUILT_IN_NONE); + if (target) + return target; + break; - return NULL_TREE; -} + case BUILT_IN_ATOMIC_TEST_AND_SET: + return expand_builtin_atomic_test_and_set (exp, target); -/* Fold a call to builtin modf. */ + case BUILT_IN_ATOMIC_CLEAR: + return expand_builtin_atomic_clear (exp); + + case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: + return expand_builtin_atomic_always_lock_free (exp); -static tree -fold_builtin_modf (location_t loc, tree arg0, tree arg1, tree rettype) -{ - if (! validate_arg (arg0, REAL_TYPE) || ! validate_arg (arg1, POINTER_TYPE)) - return NULL_TREE; + case BUILT_IN_ATOMIC_IS_LOCK_FREE: + target = expand_builtin_atomic_is_lock_free (exp); + if (target) + return target; + break; - STRIP_NOPS (arg0); + case BUILT_IN_ATOMIC_THREAD_FENCE: + expand_builtin_atomic_thread_fence (exp); + return const0_rtx; - if (!(TREE_CODE (arg0) == REAL_CST && ! TREE_OVERFLOW (arg0))) - return NULL_TREE; + case BUILT_IN_ATOMIC_SIGNAL_FENCE: + expand_builtin_atomic_signal_fence (exp); + return const0_rtx; - arg1 = build_fold_indirect_ref_loc (loc, arg1); + case BUILT_IN_OBJECT_SIZE: + return expand_builtin_object_size (exp); - /* Proceed if a valid pointer type was passed in. */ - if (TYPE_MAIN_VARIANT (TREE_TYPE (arg1)) == TYPE_MAIN_VARIANT (rettype)) - { - const REAL_VALUE_TYPE *const value = TREE_REAL_CST_PTR (arg0); - REAL_VALUE_TYPE trunc, frac; + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMPCPY_CHK: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMSET_CHK: + target = expand_builtin_memory_chk (exp, target, mode, fcode); + if (target) + return target; + break; - switch (value->cl) - { - case rvc_nan: - case rvc_zero: - /* For +-NaN or +-0, return (*arg1 = arg0, arg0). */ - trunc = frac = *value; - break; - case rvc_inf: - /* For +-Inf, return (*arg1 = arg0, +-0). */ - frac = dconst0; - frac.sign = value->sign; - trunc = *value; - break; - case rvc_normal: - /* Return (*arg1 = trunc(arg0), arg0-trunc(arg0)). */ - real_trunc (&trunc, VOIDmode, value); - real_arithmetic (&frac, MINUS_EXPR, value, &trunc); - /* If the original number was negative and already - integral, then the fractional part is -0.0. */ - if (value->sign && frac.cl == rvc_zero) - frac.sign = value->sign; - break; - } + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STPCPY_CHK: + case BUILT_IN_STRNCPY_CHK: + case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRNCAT_CHK: + case BUILT_IN_SNPRINTF_CHK: + case BUILT_IN_VSNPRINTF_CHK: + maybe_emit_chk_warning (exp, fcode); + break; + + case BUILT_IN_SPRINTF_CHK: + case BUILT_IN_VSPRINTF_CHK: + maybe_emit_sprintf_chk_warning (exp, fcode); + break; - /* Create the COMPOUND_EXPR (*arg1 = trunc, frac). */ - arg1 = fold_build2_loc (loc, MODIFY_EXPR, rettype, arg1, - build_real (rettype, trunc)); - TREE_SIDE_EFFECTS (arg1) = 1; - return fold_build2_loc (loc, COMPOUND_EXPR, rettype, arg1, - build_real (rettype, frac)); + case BUILT_IN_THREAD_POINTER: + return expand_builtin_thread_pointer (exp, target); + + case BUILT_IN_SET_THREAD_POINTER: + expand_builtin_set_thread_pointer (exp); + return const0_rtx; + + case BUILT_IN_ACC_ON_DEVICE: + /* Do library call, if we failed to expand the builtin when + folding. */ + break; + + case BUILT_IN_GOACC_PARLEVEL_ID: + case BUILT_IN_GOACC_PARLEVEL_SIZE: + return expand_builtin_goacc_parlevel_id_size (exp, target, ignore); + + case BUILT_IN_SPECULATION_SAFE_VALUE_PTR: + return expand_speculation_safe_value (VOIDmode, exp, target, ignore); + + case BUILT_IN_SPECULATION_SAFE_VALUE_1: + case BUILT_IN_SPECULATION_SAFE_VALUE_2: + case BUILT_IN_SPECULATION_SAFE_VALUE_4: + case BUILT_IN_SPECULATION_SAFE_VALUE_8: + case BUILT_IN_SPECULATION_SAFE_VALUE_16: + mode = get_builtin_sync_mode (fcode - BUILT_IN_SPECULATION_SAFE_VALUE_1); + return expand_speculation_safe_value (mode, exp, target, ignore); + + default: /* just do library call, if unknown builtin */ + break; } - return NULL_TREE; + /* The switch statement above can drop through to cause the function + to be called normally. */ + return expand_call (exp, target, ignore); } -/* Given a location LOC, an interclass builtin function decl FNDECL - and its single argument ARG, return an folded expression computing - the same, or NULL_TREE if we either couldn't or didn't want to fold - (the latter happen if there's an RTL instruction available). */ +/* Determine whether a tree node represents a call to a built-in + function. If the tree T is a call to a built-in function with + the right number of arguments of the appropriate types, return + the DECL_FUNCTION_CODE of the call, e.g. BUILT_IN_SQRT. + Otherwise the return value is END_BUILTINS. */ -static tree -fold_builtin_interclass_mathfn (location_t loc, tree fndecl, tree arg) +enum built_in_function +builtin_mathfn_code (const_tree t) { - machine_mode mode; - - if (!validate_arg (arg, REAL_TYPE)) - return NULL_TREE; - - if (interclass_mathfn_icode (arg, fndecl) != CODE_FOR_nothing) - return NULL_TREE; + const_tree fndecl, arg, parmlist; + const_tree argtype, parmtype; + const_call_expr_arg_iterator iter; - mode = TYPE_MODE (TREE_TYPE (arg)); + if (TREE_CODE (t) != CALL_EXPR) + return END_BUILTINS; - bool is_ibm_extended = MODE_COMPOSITE_P (mode); + fndecl = get_callee_fndecl (t); + if (fndecl == NULL_TREE || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + return END_BUILTINS; - /* If there is no optab, try generic code. */ - switch (DECL_FUNCTION_CODE (fndecl)) + parmlist = TYPE_ARG_TYPES (TREE_TYPE (fndecl)); + init_const_call_expr_arg_iterator (t, &iter); + for (; parmlist; parmlist = TREE_CHAIN (parmlist)) { - tree result; + /* If a function doesn't take a variable number of arguments, + the last element in the list will have type `void'. */ + parmtype = TREE_VALUE (parmlist); + if (VOID_TYPE_P (parmtype)) + { + if (more_const_call_expr_args_p (&iter)) + return END_BUILTINS; + return DECL_FUNCTION_CODE (fndecl); + } - CASE_FLT_FN (BUILT_IN_ISINF): - { - /* isinf(x) -> isgreater(fabs(x),DBL_MAX). */ - tree const isgr_fn = builtin_decl_explicit (BUILT_IN_ISGREATER); - tree type = TREE_TYPE (arg); - REAL_VALUE_TYPE r; - char buf[128]; + if (! more_const_call_expr_args_p (&iter)) + return END_BUILTINS; - if (is_ibm_extended) - { - /* NaN and Inf are encoded in the high-order double value - only. The low-order value is not significant. */ - type = double_type_node; - mode = DFmode; - arg = fold_build1_loc (loc, NOP_EXPR, type, arg); - } - get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); - real_from_string (&r, buf); - result = build_call_expr (isgr_fn, 2, - fold_build1_loc (loc, ABS_EXPR, type, arg), - build_real (type, r)); - return result; - } - CASE_FLT_FN (BUILT_IN_FINITE): - case BUILT_IN_ISFINITE: - { - /* isfinite(x) -> islessequal(fabs(x),DBL_MAX). */ - tree const isle_fn = builtin_decl_explicit (BUILT_IN_ISLESSEQUAL); - tree type = TREE_TYPE (arg); - REAL_VALUE_TYPE r; - char buf[128]; + arg = next_const_call_expr_arg (&iter); + argtype = TREE_TYPE (arg); - if (is_ibm_extended) - { - /* NaN and Inf are encoded in the high-order double value - only. The low-order value is not significant. */ - type = double_type_node; - mode = DFmode; - arg = fold_build1_loc (loc, NOP_EXPR, type, arg); - } - get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); - real_from_string (&r, buf); - result = build_call_expr (isle_fn, 2, - fold_build1_loc (loc, ABS_EXPR, type, arg), - build_real (type, r)); - /*result = fold_build2_loc (loc, UNGT_EXPR, - TREE_TYPE (TREE_TYPE (fndecl)), - fold_build1_loc (loc, ABS_EXPR, type, arg), - build_real (type, r)); - result = fold_build1_loc (loc, TRUTH_NOT_EXPR, - TREE_TYPE (TREE_TYPE (fndecl)), - result);*/ - return result; - } - case BUILT_IN_ISNORMAL: - { - /* isnormal(x) -> isgreaterequal(fabs(x),DBL_MIN) & - islessequal(fabs(x),DBL_MAX). */ - tree const isle_fn = builtin_decl_explicit (BUILT_IN_ISLESSEQUAL); - tree type = TREE_TYPE (arg); - tree orig_arg, max_exp, min_exp; - machine_mode orig_mode = mode; - REAL_VALUE_TYPE rmax, rmin; - char buf[128]; + if (SCALAR_FLOAT_TYPE_P (parmtype)) + { + if (! SCALAR_FLOAT_TYPE_P (argtype)) + return END_BUILTINS; + } + else if (COMPLEX_FLOAT_TYPE_P (parmtype)) + { + if (! COMPLEX_FLOAT_TYPE_P (argtype)) + return END_BUILTINS; + } + else if (POINTER_TYPE_P (parmtype)) + { + if (! POINTER_TYPE_P (argtype)) + return END_BUILTINS; + } + else if (INTEGRAL_TYPE_P (parmtype)) + { + if (! INTEGRAL_TYPE_P (argtype)) + return END_BUILTINS; + } + else + return END_BUILTINS; + } - orig_arg = arg = builtin_save_expr (arg); - if (is_ibm_extended) - { - /* Use double to test the normal range of IBM extended - precision. Emin for IBM extended precision is - different to emin for IEEE double, being 53 higher - since the low double exponent is at least 53 lower - than the high double exponent. */ - type = double_type_node; - mode = DFmode; - arg = fold_build1_loc (loc, NOP_EXPR, type, arg); - } - arg = fold_build1_loc (loc, ABS_EXPR, type, arg); + /* Variable-length argument list. */ + return DECL_FUNCTION_CODE (fndecl); +} - get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); - real_from_string (&rmax, buf); - sprintf (buf, "0x1p%d", REAL_MODE_FORMAT (orig_mode)->emin - 1); - real_from_string (&rmin, buf); - max_exp = build_real (type, rmax); - min_exp = build_real (type, rmin); +/* Fold a call to __builtin_constant_p, if we know its argument ARG will + evaluate to a constant. */ - max_exp = build_call_expr (isle_fn, 2, arg, max_exp); - if (is_ibm_extended) - { - /* Testing the high end of the range is done just using - the high double, using the same test as isfinite(). - For the subnormal end of the range we first test the - high double, then if its magnitude is equal to the - limit of 0x1p-969, we test whether the low double is - non-zero and opposite sign to the high double. */ - tree const islt_fn = builtin_decl_explicit (BUILT_IN_ISLESS); - tree const isgt_fn = builtin_decl_explicit (BUILT_IN_ISGREATER); - tree gt_min = build_call_expr (isgt_fn, 2, arg, min_exp); - tree eq_min = fold_build2 (EQ_EXPR, integer_type_node, - arg, min_exp); - tree as_complex = build1 (VIEW_CONVERT_EXPR, - complex_double_type_node, orig_arg); - tree hi_dbl = build1 (REALPART_EXPR, type, as_complex); - tree lo_dbl = build1 (IMAGPART_EXPR, type, as_complex); - tree zero = build_real (type, dconst0); - tree hilt = build_call_expr (islt_fn, 2, hi_dbl, zero); - tree lolt = build_call_expr (islt_fn, 2, lo_dbl, zero); - tree logt = build_call_expr (isgt_fn, 2, lo_dbl, zero); - tree ok_lo = fold_build1 (TRUTH_NOT_EXPR, integer_type_node, - fold_build3 (COND_EXPR, - integer_type_node, - hilt, logt, lolt)); - eq_min = fold_build2 (TRUTH_ANDIF_EXPR, integer_type_node, - eq_min, ok_lo); - min_exp = fold_build2 (TRUTH_ORIF_EXPR, integer_type_node, - gt_min, eq_min); - } - else - { - tree const isge_fn - = builtin_decl_explicit (BUILT_IN_ISGREATEREQUAL); - min_exp = build_call_expr (isge_fn, 2, arg, min_exp); - } - result = fold_build2 (BIT_AND_EXPR, integer_type_node, - max_exp, min_exp); - return result; - } - default: - break; +static tree +fold_builtin_constant_p (tree arg) +{ + /* We return 1 for a numeric type that's known to be a constant + value at compile-time or for an aggregate type that's a + literal constant. */ + STRIP_NOPS (arg); + + /* If we know this is a constant, emit the constant of one. */ + if (CONSTANT_CLASS_P (arg) + || (TREE_CODE (arg) == CONSTRUCTOR + && TREE_CONSTANT (arg))) + return integer_one_node; + if (TREE_CODE (arg) == ADDR_EXPR) + { + tree op = TREE_OPERAND (arg, 0); + if (TREE_CODE (op) == STRING_CST + || (TREE_CODE (op) == ARRAY_REF + && integer_zerop (TREE_OPERAND (op, 1)) + && TREE_CODE (TREE_OPERAND (op, 0)) == STRING_CST)) + return integer_one_node; } + /* If this expression has side effects, show we don't know it to be a + constant. Likewise if it's a pointer or aggregate type since in + those case we only want literals, since those are only optimized + when generating RTL, not later. + And finally, if we are compiling an initializer, not code, we + need to return a definite result now; there's not going to be any + more optimization done. */ + if (TREE_SIDE_EFFECTS (arg) + || AGGREGATE_TYPE_P (TREE_TYPE (arg)) + || POINTER_TYPE_P (TREE_TYPE (arg)) + || cfun == 0 + || folding_initializer + || force_folding_builtin_constant_p) + return integer_zero_node; + return NULL_TREE; } -/* Fold a call to __builtin_isnan(), __builtin_isinf, __builtin_finite. - ARG is the argument for the call. */ +/* Create builtin_expect or builtin_expect_with_probability + with PRED and EXPECTED as its arguments and return it as a truthvalue. + Fortran FE can also produce builtin_expect with PREDICTOR as third argument. + builtin_expect_with_probability instead uses third argument as PROBABILITY + value. */ static tree -fold_builtin_classify (location_t loc, tree fndecl, tree arg, int builtin_index) +build_builtin_expect_predicate (location_t loc, tree pred, tree expected, + tree predictor, tree probability) { - tree type = TREE_TYPE (TREE_TYPE (fndecl)); + tree fn, arg_types, pred_type, expected_type, call_expr, ret_type; - if (!validate_arg (arg, REAL_TYPE)) - return NULL_TREE; + fn = builtin_decl_explicit (probability == NULL_TREE ? BUILT_IN_EXPECT + : BUILT_IN_EXPECT_WITH_PROBABILITY); + arg_types = TYPE_ARG_TYPES (TREE_TYPE (fn)); + ret_type = TREE_TYPE (TREE_TYPE (fn)); + pred_type = TREE_VALUE (arg_types); + expected_type = TREE_VALUE (TREE_CHAIN (arg_types)); - switch (builtin_index) - { - case BUILT_IN_ISINF: - if (tree_expr_infinite_p (arg)) - return omit_one_operand_loc (loc, type, integer_one_node, arg); - if (!tree_expr_maybe_infinite_p (arg)) - return omit_one_operand_loc (loc, type, integer_zero_node, arg); - return NULL_TREE; + pred = fold_convert_loc (loc, pred_type, pred); + expected = fold_convert_loc (loc, expected_type, expected); - case BUILT_IN_ISINF_SIGN: - { - /* isinf_sign(x) -> isinf(x) ? (signbit(x) ? -1 : 1) : 0 */ - /* In a boolean context, GCC will fold the inner COND_EXPR to - 1. So e.g. "if (isinf_sign(x))" would be folded to just - "if (isinf(x) ? 1 : 0)" which becomes "if (isinf(x))". */ - tree signbit_fn = builtin_decl_explicit (BUILT_IN_SIGNBIT); - tree isinf_fn = builtin_decl_explicit (BUILT_IN_ISINF); - tree tmp = NULL_TREE; + if (probability) + call_expr = build_call_expr_loc (loc, fn, 3, pred, expected, probability); + else + call_expr = build_call_expr_loc (loc, fn, predictor ? 3 : 2, pred, expected, + predictor); - arg = builtin_save_expr (arg); + return build2 (NE_EXPR, TREE_TYPE (pred), call_expr, + build_int_cst (ret_type, 0)); +} - if (signbit_fn && isinf_fn) - { - tree signbit_call = build_call_expr_loc (loc, signbit_fn, 1, arg); - tree isinf_call = build_call_expr_loc (loc, isinf_fn, 1, arg); +/* Fold a call to builtin_expect with arguments ARG0, ARG1, ARG2, ARG3. Return + NULL_TREE if no simplification is possible. */ - signbit_call = fold_build2_loc (loc, NE_EXPR, integer_type_node, - signbit_call, integer_zero_node); - isinf_call = fold_build2_loc (loc, NE_EXPR, integer_type_node, - isinf_call, integer_zero_node); +tree +fold_builtin_expect (location_t loc, tree arg0, tree arg1, tree arg2, + tree arg3) +{ + tree inner, fndecl, inner_arg0; + enum tree_code code; - tmp = fold_build3_loc (loc, COND_EXPR, integer_type_node, signbit_call, - integer_minus_one_node, integer_one_node); - tmp = fold_build3_loc (loc, COND_EXPR, integer_type_node, - isinf_call, tmp, - integer_zero_node); - } + /* Distribute the expected value over short-circuiting operators. + See through the cast from truthvalue_type_node to long. */ + inner_arg0 = arg0; + while (CONVERT_EXPR_P (inner_arg0) + && INTEGRAL_TYPE_P (TREE_TYPE (inner_arg0)) + && INTEGRAL_TYPE_P (TREE_TYPE (TREE_OPERAND (inner_arg0, 0)))) + inner_arg0 = TREE_OPERAND (inner_arg0, 0); - return tmp; - } + /* If this is a builtin_expect within a builtin_expect keep the + inner one. See through a comparison against a constant. It + might have been added to create a thruthvalue. */ + inner = inner_arg0; - case BUILT_IN_ISFINITE: - if (tree_expr_finite_p (arg)) - return omit_one_operand_loc (loc, type, integer_one_node, arg); - if (tree_expr_nan_p (arg) || tree_expr_infinite_p (arg)) - return omit_one_operand_loc (loc, type, integer_zero_node, arg); - return NULL_TREE; + if (COMPARISON_CLASS_P (inner) + && TREE_CODE (TREE_OPERAND (inner, 1)) == INTEGER_CST) + inner = TREE_OPERAND (inner, 0); - case BUILT_IN_ISNAN: - if (tree_expr_nan_p (arg)) - return omit_one_operand_loc (loc, type, integer_one_node, arg); - if (!tree_expr_maybe_nan_p (arg)) - return omit_one_operand_loc (loc, type, integer_zero_node, arg); + if (TREE_CODE (inner) == CALL_EXPR + && (fndecl = get_callee_fndecl (inner)) + && (fndecl_built_in_p (fndecl, BUILT_IN_EXPECT) + || fndecl_built_in_p (fndecl, BUILT_IN_EXPECT_WITH_PROBABILITY))) + return arg0; - { - bool is_ibm_extended = MODE_COMPOSITE_P (TYPE_MODE (TREE_TYPE (arg))); - if (is_ibm_extended) - { - /* NaN and Inf are encoded in the high-order double value - only. The low-order value is not significant. */ - arg = fold_build1_loc (loc, NOP_EXPR, double_type_node, arg); - } - } - arg = builtin_save_expr (arg); - return fold_build2_loc (loc, UNORDERED_EXPR, type, arg, arg); + inner = inner_arg0; + code = TREE_CODE (inner); + if (code == TRUTH_ANDIF_EXPR || code == TRUTH_ORIF_EXPR) + { + tree op0 = TREE_OPERAND (inner, 0); + tree op1 = TREE_OPERAND (inner, 1); + arg1 = save_expr (arg1); - default: - gcc_unreachable (); + op0 = build_builtin_expect_predicate (loc, op0, arg1, arg2, arg3); + op1 = build_builtin_expect_predicate (loc, op1, arg1, arg2, arg3); + inner = build2 (code, TREE_TYPE (inner), op0, op1); + + return fold_convert_loc (loc, TREE_TYPE (arg0), inner); + } + + /* If the argument isn't invariant then there's nothing else we can do. */ + if (!TREE_CONSTANT (inner_arg0)) + return NULL_TREE; + + /* If we expect that a comparison against the argument will fold to + a constant return the constant. In practice, this means a true + constant or the address of a non-weak symbol. */ + inner = inner_arg0; + STRIP_NOPS (inner); + if (TREE_CODE (inner) == ADDR_EXPR) + { + do + { + inner = TREE_OPERAND (inner, 0); + } + while (TREE_CODE (inner) == COMPONENT_REF + || TREE_CODE (inner) == ARRAY_REF); + if (VAR_OR_FUNCTION_DECL_P (inner) && DECL_WEAK (inner)) + return NULL_TREE; } + + /* Otherwise, ARG0 already has the proper type for the return value. */ + return arg0; } -/* Fold a call to __builtin_fpclassify(int, int, int, int, int, ...). - This builtin will generate code to return the appropriate floating - point classification depending on the value of the floating point - number passed in. The possible return values must be supplied as - int arguments to the call in the following order: FP_NAN, FP_INFINITE, - FP_NORMAL, FP_SUBNORMAL and FP_ZERO. The ellipses is for exactly - one floating point argument which is "type generic". */ +/* Fold a call to __builtin_classify_type with argument ARG. */ static tree -fold_builtin_fpclassify (location_t loc, tree *args, int nargs) +fold_builtin_classify_type (tree arg) { - tree fp_nan, fp_infinite, fp_normal, fp_subnormal, fp_zero, - arg, type, res, tmp; - machine_mode mode; - REAL_VALUE_TYPE r; - char buf[128]; + if (arg == 0) + return build_int_cst (integer_type_node, no_type_class); - /* Verify the required arguments in the original call. */ - if (nargs != 6 - || !validate_arg (args[0], INTEGER_TYPE) - || !validate_arg (args[1], INTEGER_TYPE) - || !validate_arg (args[2], INTEGER_TYPE) - || !validate_arg (args[3], INTEGER_TYPE) - || !validate_arg (args[4], INTEGER_TYPE) - || !validate_arg (args[5], REAL_TYPE)) + return build_int_cst (integer_type_node, type_to_class (TREE_TYPE (arg))); +} + +/* Fold a call EXPR (which may be null) to __builtin_strlen with argument + ARG. */ + +static tree +fold_builtin_strlen (location_t loc, tree expr, tree type, tree arg) +{ + if (!validate_arg (arg, POINTER_TYPE)) return NULL_TREE; + else + { + c_strlen_data lendata = { }; + tree len = c_strlen (arg, 0, &lendata); - fp_nan = args[0]; - fp_infinite = args[1]; - fp_normal = args[2]; - fp_subnormal = args[3]; - fp_zero = args[4]; - arg = args[5]; - type = TREE_TYPE (arg); - mode = TYPE_MODE (type); - arg = builtin_save_expr (fold_build1_loc (loc, ABS_EXPR, type, arg)); + if (len) + return fold_convert_loc (loc, type, len); + + if (!lendata.decl) + c_strlen (arg, 1, &lendata); + + if (lendata.decl) + { + if (EXPR_HAS_LOCATION (arg)) + loc = EXPR_LOCATION (arg); + else if (loc == UNKNOWN_LOCATION) + loc = input_location; + warn_string_no_nul (loc, expr, "strlen", arg, lendata.decl); + } + + return NULL_TREE; + } +} + +/* Fold a call to __builtin_inf or __builtin_huge_val. */ + +static tree +fold_builtin_inf (location_t loc, tree type, int warn) +{ + REAL_VALUE_TYPE real; + + /* __builtin_inff is intended to be usable to define INFINITY on all + targets. If an infinity is not available, INFINITY expands "to a + positive constant of type float that overflows at translation + time", footnote "In this case, using INFINITY will violate the + constraint in 6.4.4 and thus require a diagnostic." (C99 7.12#4). + Thus we pedwarn to ensure this constraint violation is + diagnosed. */ + if (!MODE_HAS_INFINITIES (TYPE_MODE (type)) && warn) + pedwarn (loc, 0, "target format does not support infinity"); - /* fpclassify(x) -> - isnan(x) ? FP_NAN : - (fabs(x) == Inf ? FP_INFINITE : - (fabs(x) >= DBL_MIN ? FP_NORMAL : - (x == 0 ? FP_ZERO : FP_SUBNORMAL))). */ + real_inf (&real); + return build_real (type, real); +} - tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, arg, - build_real (type, dconst0)); - res = fold_build3_loc (loc, COND_EXPR, integer_type_node, - tmp, fp_zero, fp_subnormal); +/* Fold function call to builtin sincos, sincosf, or sincosl. Return + NULL_TREE if no simplification can be made. */ - sprintf (buf, "0x1p%d", REAL_MODE_FORMAT (mode)->emin - 1); - real_from_string (&r, buf); - tmp = fold_build2_loc (loc, GE_EXPR, integer_type_node, - arg, build_real (type, r)); - res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, fp_normal, res); +static tree +fold_builtin_sincos (location_t loc, + tree arg0, tree arg1, tree arg2) +{ + tree type; + tree fndecl, call = NULL_TREE; - if (tree_expr_maybe_infinite_p (arg)) + if (!validate_arg (arg0, REAL_TYPE) + || !validate_arg (arg1, POINTER_TYPE) + || !validate_arg (arg2, POINTER_TYPE)) + return NULL_TREE; + + type = TREE_TYPE (arg0); + + /* Calculate the result when the argument is a constant. */ + built_in_function fn = mathfn_built_in_2 (type, CFN_BUILT_IN_CEXPI); + if (fn == END_BUILTINS) + return NULL_TREE; + + /* Canonicalize sincos to cexpi. */ + if (TREE_CODE (arg0) == REAL_CST) { - real_inf (&r); - tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, arg, - build_real (type, r)); - res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, - fp_infinite, res); + tree complex_type = build_complex_type (type); + call = fold_const_call (as_combined_fn (fn), complex_type, arg0); } - - if (tree_expr_maybe_nan_p (arg)) + if (!call) { - tmp = fold_build2_loc (loc, ORDERED_EXPR, integer_type_node, arg, arg); - res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, res, fp_nan); + if (!targetm.libc_has_function (function_c99_math_complex, type) + || !builtin_decl_implicit_p (fn)) + return NULL_TREE; + fndecl = builtin_decl_explicit (fn); + call = build_call_expr_loc (loc, fndecl, 1, arg0); + call = builtin_save_expr (call); } - return res; + tree ptype = build_pointer_type (type); + arg1 = fold_convert (ptype, arg1); + arg2 = fold_convert (ptype, arg2); + return build2 (COMPOUND_EXPR, void_type_node, + build2 (MODIFY_EXPR, void_type_node, + build_fold_indirect_ref_loc (loc, arg1), + fold_build1_loc (loc, IMAGPART_EXPR, type, call)), + build2 (MODIFY_EXPR, void_type_node, + build_fold_indirect_ref_loc (loc, arg2), + fold_build1_loc (loc, REALPART_EXPR, type, call))); } -/* Fold a call to an unordered comparison function such as - __builtin_isgreater(). FNDECL is the FUNCTION_DECL for the function - being called and ARG0 and ARG1 are the arguments for the call. - UNORDERED_CODE and ORDERED_CODE are comparison codes that give - the opposite of the desired result. UNORDERED_CODE is used - for modes that can hold NaNs and ORDERED_CODE is used for - the rest. */ +/* Fold function call to builtin memcmp with arguments ARG1 and ARG2. + Return NULL_TREE if no simplification can be made. */ static tree -fold_builtin_unordered_cmp (location_t loc, tree fndecl, tree arg0, tree arg1, - enum tree_code unordered_code, - enum tree_code ordered_code) +fold_builtin_memcmp (location_t loc, tree arg1, tree arg2, tree len) { - tree type = TREE_TYPE (TREE_TYPE (fndecl)); - enum tree_code code; - tree type0, type1; - enum tree_code code0, code1; - tree cmp_type = NULL_TREE; - - type0 = TREE_TYPE (arg0); - type1 = TREE_TYPE (arg1); - - code0 = TREE_CODE (type0); - code1 = TREE_CODE (type1); + if (!validate_arg (arg1, POINTER_TYPE) + || !validate_arg (arg2, POINTER_TYPE) + || !validate_arg (len, INTEGER_TYPE)) + return NULL_TREE; - if (code0 == REAL_TYPE && code1 == REAL_TYPE) - /* Choose the wider of two real types. */ - cmp_type = TYPE_PRECISION (type0) >= TYPE_PRECISION (type1) - ? type0 : type1; - else if (code0 == REAL_TYPE && code1 == INTEGER_TYPE) - cmp_type = type0; - else if (code0 == INTEGER_TYPE && code1 == REAL_TYPE) - cmp_type = type1; + /* If the LEN parameter is zero, return zero. */ + if (integer_zerop (len)) + return omit_two_operands_loc (loc, integer_type_node, integer_zero_node, + arg1, arg2); - arg0 = fold_convert_loc (loc, cmp_type, arg0); - arg1 = fold_convert_loc (loc, cmp_type, arg1); + /* If ARG1 and ARG2 are the same (and not volatile), return zero. */ + if (operand_equal_p (arg1, arg2, 0)) + return omit_one_operand_loc (loc, integer_type_node, integer_zero_node, len); - if (unordered_code == UNORDERED_EXPR) + /* If len parameter is one, return an expression corresponding to + (*(const unsigned char*)arg1 - (const unsigned char*)arg2). */ + if (tree_fits_uhwi_p (len) && tree_to_uhwi (len) == 1) { - if (tree_expr_nan_p (arg0) || tree_expr_nan_p (arg1)) - return omit_two_operands_loc (loc, type, integer_one_node, arg0, arg1); - if (!tree_expr_maybe_nan_p (arg0) && !tree_expr_maybe_nan_p (arg1)) - return omit_two_operands_loc (loc, type, integer_zero_node, arg0, arg1); - return fold_build2_loc (loc, UNORDERED_EXPR, type, arg0, arg1); + tree cst_uchar_node = build_type_variant (unsigned_char_type_node, 1, 0); + tree cst_uchar_ptr_node + = build_pointer_type_for_mode (cst_uchar_node, ptr_mode, true); + + tree ind1 + = fold_convert_loc (loc, integer_type_node, + build1 (INDIRECT_REF, cst_uchar_node, + fold_convert_loc (loc, + cst_uchar_ptr_node, + arg1))); + tree ind2 + = fold_convert_loc (loc, integer_type_node, + build1 (INDIRECT_REF, cst_uchar_node, + fold_convert_loc (loc, + cst_uchar_ptr_node, + arg2))); + return fold_build2_loc (loc, MINUS_EXPR, integer_type_node, ind1, ind2); } - code = (tree_expr_maybe_nan_p (arg0) || tree_expr_maybe_nan_p (arg1)) - ? unordered_code : ordered_code; - return fold_build1_loc (loc, TRUTH_NOT_EXPR, type, - fold_build2_loc (loc, code, type, arg0, arg1)); + return NULL_TREE; } -/* Fold __builtin_{,s,u}{add,sub,mul}{,l,ll}_overflow, either into normal - arithmetics if it can never overflow, or into internal functions that - return both result of arithmetics and overflowed boolean flag in - a complex integer result, or some other check for overflow. - Similarly fold __builtin_{add,sub,mul}_overflow_p to just the overflow - checking part of that. */ +/* Fold a call to builtin isascii with argument ARG. */ static tree -fold_builtin_arith_overflow (location_t loc, enum built_in_function fcode, - tree arg0, tree arg1, tree arg2) +fold_builtin_isascii (location_t loc, tree arg) { - enum internal_fn ifn = IFN_LAST; - /* The code of the expression corresponding to the built-in. */ - enum tree_code opcode = ERROR_MARK; - bool ovf_only = false; - - switch (fcode) + if (!validate_arg (arg, INTEGER_TYPE)) + return NULL_TREE; + else { - case BUILT_IN_ADD_OVERFLOW_P: - ovf_only = true; - /* FALLTHRU */ - case BUILT_IN_ADD_OVERFLOW: - case BUILT_IN_SADD_OVERFLOW: - case BUILT_IN_SADDL_OVERFLOW: - case BUILT_IN_SADDLL_OVERFLOW: - case BUILT_IN_UADD_OVERFLOW: - case BUILT_IN_UADDL_OVERFLOW: - case BUILT_IN_UADDLL_OVERFLOW: - opcode = PLUS_EXPR; - ifn = IFN_ADD_OVERFLOW; - break; - case BUILT_IN_SUB_OVERFLOW_P: - ovf_only = true; - /* FALLTHRU */ - case BUILT_IN_SUB_OVERFLOW: - case BUILT_IN_SSUB_OVERFLOW: - case BUILT_IN_SSUBL_OVERFLOW: - case BUILT_IN_SSUBLL_OVERFLOW: - case BUILT_IN_USUB_OVERFLOW: - case BUILT_IN_USUBL_OVERFLOW: - case BUILT_IN_USUBLL_OVERFLOW: - opcode = MINUS_EXPR; - ifn = IFN_SUB_OVERFLOW; - break; - case BUILT_IN_MUL_OVERFLOW_P: - ovf_only = true; - /* FALLTHRU */ - case BUILT_IN_MUL_OVERFLOW: - case BUILT_IN_SMUL_OVERFLOW: - case BUILT_IN_SMULL_OVERFLOW: - case BUILT_IN_SMULLL_OVERFLOW: - case BUILT_IN_UMUL_OVERFLOW: - case BUILT_IN_UMULL_OVERFLOW: - case BUILT_IN_UMULLL_OVERFLOW: - opcode = MULT_EXPR; - ifn = IFN_MUL_OVERFLOW; - break; - default: - gcc_unreachable (); + /* Transform isascii(c) -> ((c & ~0x7f) == 0). */ + arg = fold_build2 (BIT_AND_EXPR, integer_type_node, arg, + build_int_cst (integer_type_node, + ~ (unsigned HOST_WIDE_INT) 0x7f)); + return fold_build2_loc (loc, EQ_EXPR, integer_type_node, + arg, integer_zero_node); } +} - /* For the "generic" overloads, the first two arguments can have different - types and the last argument determines the target type to use to check - for overflow. The arguments of the other overloads all have the same - type. */ - tree type = ovf_only ? TREE_TYPE (arg2) : TREE_TYPE (TREE_TYPE (arg2)); +/* Fold a call to builtin toascii with argument ARG. */ - /* For the __builtin_{add,sub,mul}_overflow_p builtins, when the first two - arguments are constant, attempt to fold the built-in call into a constant - expression indicating whether or not it detected an overflow. */ - if (ovf_only - && TREE_CODE (arg0) == INTEGER_CST - && TREE_CODE (arg1) == INTEGER_CST) - /* Perform the computation in the target type and check for overflow. */ - return omit_one_operand_loc (loc, boolean_type_node, - arith_overflowed_p (opcode, type, arg0, arg1) - ? boolean_true_node : boolean_false_node, - arg2); +static tree +fold_builtin_toascii (location_t loc, tree arg) +{ + if (!validate_arg (arg, INTEGER_TYPE)) + return NULL_TREE; - tree intres, ovfres; - if (TREE_CODE (arg0) == INTEGER_CST && TREE_CODE (arg1) == INTEGER_CST) - { - intres = fold_binary_loc (loc, opcode, type, - fold_convert_loc (loc, type, arg0), - fold_convert_loc (loc, type, arg1)); - if (TREE_OVERFLOW (intres)) - intres = drop_tree_overflow (intres); - ovfres = (arith_overflowed_p (opcode, type, arg0, arg1) - ? boolean_true_node : boolean_false_node); - } + /* Transform toascii(c) -> (c & 0x7f). */ + return fold_build2_loc (loc, BIT_AND_EXPR, integer_type_node, arg, + build_int_cst (integer_type_node, 0x7f)); +} + +/* Fold a call to builtin isdigit with argument ARG. */ + +static tree +fold_builtin_isdigit (location_t loc, tree arg) +{ + if (!validate_arg (arg, INTEGER_TYPE)) + return NULL_TREE; else { - tree ctype = build_complex_type (type); - tree call = build_call_expr_internal_loc (loc, ifn, ctype, 2, - arg0, arg1); - tree tgt = save_expr (call); - intres = build1_loc (loc, REALPART_EXPR, type, tgt); - ovfres = build1_loc (loc, IMAGPART_EXPR, type, tgt); - ovfres = fold_convert_loc (loc, boolean_type_node, ovfres); - } + /* Transform isdigit(c) -> (unsigned)(c) - '0' <= 9. */ + /* According to the C standard, isdigit is unaffected by locale. + However, it definitely is affected by the target character set. */ + unsigned HOST_WIDE_INT target_digit0 + = lang_hooks.to_target_charset ('0'); - if (ovf_only) - return omit_one_operand_loc (loc, boolean_type_node, ovfres, arg2); + if (target_digit0 == 0) + return NULL_TREE; - tree mem_arg2 = build_fold_indirect_ref_loc (loc, arg2); - tree store - = fold_build2_loc (loc, MODIFY_EXPR, void_type_node, mem_arg2, intres); - return build2_loc (loc, COMPOUND_EXPR, boolean_type_node, store, ovfres); + arg = fold_convert_loc (loc, unsigned_type_node, arg); + arg = fold_build2 (MINUS_EXPR, unsigned_type_node, arg, + build_int_cst (unsigned_type_node, target_digit0)); + return fold_build2_loc (loc, LE_EXPR, integer_type_node, arg, + build_int_cst (unsigned_type_node, 9)); + } } -/* Fold a call to __builtin_FILE to a constant string. */ +/* Fold a call to fabs, fabsf or fabsl with argument ARG. */ -static inline tree -fold_builtin_FILE (location_t loc) +static tree +fold_builtin_fabs (location_t loc, tree arg, tree type) { - if (const char *fname = LOCATION_FILE (loc)) - { - /* The documentation says this builtin is equivalent to the preprocessor - __FILE__ macro so it appears appropriate to use the same file prefix - mappings. */ - fname = remap_macro_filename (fname); - return build_string_literal (strlen (fname) + 1, fname); - } + if (!validate_arg (arg, REAL_TYPE)) + return NULL_TREE; - return build_string_literal (1, ""); + arg = fold_convert_loc (loc, type, arg); + return fold_build1_loc (loc, ABS_EXPR, type, arg); } -/* Fold a call to __builtin_FUNCTION to a constant string. */ +/* Fold a call to abs, labs, llabs or imaxabs with argument ARG. */ -static inline tree -fold_builtin_FUNCTION () +static tree +fold_builtin_abs (location_t loc, tree arg, tree type) { - const char *name = ""; - - if (current_function_decl) - name = lang_hooks.decl_printable_name (current_function_decl, 0); + if (!validate_arg (arg, INTEGER_TYPE)) + return NULL_TREE; - return build_string_literal (strlen (name) + 1, name); + arg = fold_convert_loc (loc, type, arg); + return fold_build1_loc (loc, ABS_EXPR, type, arg); } -/* Fold a call to __builtin_LINE to an integer constant. */ +/* Fold a call to builtin carg(a+bi) -> atan2(b,a). */ -static inline tree -fold_builtin_LINE (location_t loc, tree type) +static tree +fold_builtin_carg (location_t loc, tree arg, tree type) { - return build_int_cst (type, LOCATION_LINE (loc)); + if (validate_arg (arg, COMPLEX_TYPE) + && TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == REAL_TYPE) + { + tree atan2_fn = mathfn_built_in (type, BUILT_IN_ATAN2); + + if (atan2_fn) + { + tree new_arg = builtin_save_expr (arg); + tree r_arg = fold_build1_loc (loc, REALPART_EXPR, type, new_arg); + tree i_arg = fold_build1_loc (loc, IMAGPART_EXPR, type, new_arg); + return build_call_expr_loc (loc, atan2_fn, 2, i_arg, r_arg); + } + } + + return NULL_TREE; } -/* Fold a call to built-in function FNDECL with 0 arguments. - This function returns NULL_TREE if no simplification was possible. */ +/* Fold a call to builtin frexp, we can assume the base is 2. */ static tree -fold_builtin_0 (location_t loc, tree fndecl) +fold_builtin_frexp (location_t loc, tree arg0, tree arg1, tree rettype) { - tree type = TREE_TYPE (TREE_TYPE (fndecl)); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - switch (fcode) - { - case BUILT_IN_FILE: - return fold_builtin_FILE (loc); + if (! validate_arg (arg0, REAL_TYPE) || ! validate_arg (arg1, POINTER_TYPE)) + return NULL_TREE; - case BUILT_IN_FUNCTION: - return fold_builtin_FUNCTION (); + STRIP_NOPS (arg0); - case BUILT_IN_LINE: - return fold_builtin_LINE (loc, type); + if (!(TREE_CODE (arg0) == REAL_CST && ! TREE_OVERFLOW (arg0))) + return NULL_TREE; - CASE_FLT_FN (BUILT_IN_INF): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_INF): - case BUILT_IN_INFD32: - case BUILT_IN_INFD64: - case BUILT_IN_INFD128: - return fold_builtin_inf (loc, type, true); + arg1 = build_fold_indirect_ref_loc (loc, arg1); - CASE_FLT_FN (BUILT_IN_HUGE_VAL): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_HUGE_VAL): - return fold_builtin_inf (loc, type, false); + /* Proceed if a valid pointer type was passed in. */ + if (TYPE_MAIN_VARIANT (TREE_TYPE (arg1)) == integer_type_node) + { + const REAL_VALUE_TYPE *const value = TREE_REAL_CST_PTR (arg0); + tree frac, exp; - case BUILT_IN_CLASSIFY_TYPE: - return fold_builtin_classify_type (NULL_TREE); + switch (value->cl) + { + case rvc_zero: + /* For +-0, return (*exp = 0, +-0). */ + exp = integer_zero_node; + frac = arg0; + break; + case rvc_nan: + case rvc_inf: + /* For +-NaN or +-Inf, *exp is unspecified, return arg0. */ + return omit_one_operand_loc (loc, rettype, arg0, arg1); + case rvc_normal: + { + /* Since the frexp function always expects base 2, and in + GCC normalized significands are already in the range + [0.5, 1.0), we have exactly what frexp wants. */ + REAL_VALUE_TYPE frac_rvt = *value; + SET_REAL_EXP (&frac_rvt, 0); + frac = build_real (rettype, frac_rvt); + exp = build_int_cst (integer_type_node, REAL_EXP (value)); + } + break; + default: + gcc_unreachable (); + } - default: - break; + /* Create the COMPOUND_EXPR (*arg1 = trunc, frac). */ + arg1 = fold_build2_loc (loc, MODIFY_EXPR, rettype, arg1, exp); + TREE_SIDE_EFFECTS (arg1) = 1; + return fold_build2_loc (loc, COMPOUND_EXPR, rettype, arg1, frac); } + return NULL_TREE; } -/* Fold a call to built-in function FNDECL with 1 argument, ARG0. - This function returns NULL_TREE if no simplification was possible. */ +/* Fold a call to builtin modf. */ static tree -fold_builtin_1 (location_t loc, tree expr, tree fndecl, tree arg0) +fold_builtin_modf (location_t loc, tree arg0, tree arg1, tree rettype) { - tree type = TREE_TYPE (TREE_TYPE (fndecl)); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + if (! validate_arg (arg0, REAL_TYPE) || ! validate_arg (arg1, POINTER_TYPE)) + return NULL_TREE; - if (TREE_CODE (arg0) == ERROR_MARK) + STRIP_NOPS (arg0); + + if (!(TREE_CODE (arg0) == REAL_CST && ! TREE_OVERFLOW (arg0))) return NULL_TREE; - if (tree ret = fold_const_call (as_combined_fn (fcode), type, arg0)) - return ret; + arg1 = build_fold_indirect_ref_loc (loc, arg1); - switch (fcode) + /* Proceed if a valid pointer type was passed in. */ + if (TYPE_MAIN_VARIANT (TREE_TYPE (arg1)) == TYPE_MAIN_VARIANT (rettype)) { - case BUILT_IN_CONSTANT_P: - { - tree val = fold_builtin_constant_p (arg0); - - /* Gimplification will pull the CALL_EXPR for the builtin out of - an if condition. When not optimizing, we'll not CSE it back. - To avoid link error types of regressions, return false now. */ - if (!val && !optimize) - val = integer_zero_node; + const REAL_VALUE_TYPE *const value = TREE_REAL_CST_PTR (arg0); + REAL_VALUE_TYPE trunc, frac; - return val; + switch (value->cl) + { + case rvc_nan: + case rvc_zero: + /* For +-NaN or +-0, return (*arg1 = arg0, arg0). */ + trunc = frac = *value; + break; + case rvc_inf: + /* For +-Inf, return (*arg1 = arg0, +-0). */ + frac = dconst0; + frac.sign = value->sign; + trunc = *value; + break; + case rvc_normal: + /* Return (*arg1 = trunc(arg0), arg0-trunc(arg0)). */ + real_trunc (&trunc, VOIDmode, value); + real_arithmetic (&frac, MINUS_EXPR, value, &trunc); + /* If the original number was negative and already + integral, then the fractional part is -0.0. */ + if (value->sign && frac.cl == rvc_zero) + frac.sign = value->sign; + break; } - case BUILT_IN_CLASSIFY_TYPE: - return fold_builtin_classify_type (arg0); - - case BUILT_IN_STRLEN: - return fold_builtin_strlen (loc, expr, type, arg0); + /* Create the COMPOUND_EXPR (*arg1 = trunc, frac). */ + arg1 = fold_build2_loc (loc, MODIFY_EXPR, rettype, arg1, + build_real (rettype, trunc)); + TREE_SIDE_EFFECTS (arg1) = 1; + return fold_build2_loc (loc, COMPOUND_EXPR, rettype, arg1, + build_real (rettype, frac)); + } - CASE_FLT_FN (BUILT_IN_FABS): - CASE_FLT_FN_FLOATN_NX (BUILT_IN_FABS): - case BUILT_IN_FABSD32: - case BUILT_IN_FABSD64: - case BUILT_IN_FABSD128: - return fold_builtin_fabs (loc, arg0, type); + return NULL_TREE; +} - case BUILT_IN_ABS: - case BUILT_IN_LABS: - case BUILT_IN_LLABS: - case BUILT_IN_IMAXABS: - return fold_builtin_abs (loc, arg0, type); +/* Given a location LOC, an interclass builtin function decl FNDECL + and its single argument ARG, return an folded expression computing + the same, or NULL_TREE if we either couldn't or didn't want to fold + (the latter happen if there's an RTL instruction available). */ - CASE_FLT_FN (BUILT_IN_CONJ): - if (validate_arg (arg0, COMPLEX_TYPE) - && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) - return fold_build1_loc (loc, CONJ_EXPR, type, arg0); - break; +static tree +fold_builtin_interclass_mathfn (location_t loc, tree fndecl, tree arg) +{ + machine_mode mode; - CASE_FLT_FN (BUILT_IN_CREAL): - if (validate_arg (arg0, COMPLEX_TYPE) - && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) - return non_lvalue_loc (loc, fold_build1_loc (loc, REALPART_EXPR, type, arg0)); - break; + if (!validate_arg (arg, REAL_TYPE)) + return NULL_TREE; - CASE_FLT_FN (BUILT_IN_CIMAG): - if (validate_arg (arg0, COMPLEX_TYPE) - && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) - return non_lvalue_loc (loc, fold_build1_loc (loc, IMAGPART_EXPR, type, arg0)); - break; + if (interclass_mathfn_icode (arg, fndecl) != CODE_FOR_nothing) + return NULL_TREE; - CASE_FLT_FN (BUILT_IN_CARG): - return fold_builtin_carg (loc, arg0, type); + mode = TYPE_MODE (TREE_TYPE (arg)); - case BUILT_IN_ISASCII: - return fold_builtin_isascii (loc, arg0); + bool is_ibm_extended = MODE_COMPOSITE_P (mode); - case BUILT_IN_TOASCII: - return fold_builtin_toascii (loc, arg0); + /* If there is no optab, try generic code. */ + switch (DECL_FUNCTION_CODE (fndecl)) + { + tree result; - case BUILT_IN_ISDIGIT: - return fold_builtin_isdigit (loc, arg0); + CASE_FLT_FN (BUILT_IN_ISINF): + { + /* isinf(x) -> isgreater(fabs(x),DBL_MAX). */ + tree const isgr_fn = builtin_decl_explicit (BUILT_IN_ISGREATER); + tree type = TREE_TYPE (arg); + REAL_VALUE_TYPE r; + char buf[128]; + if (is_ibm_extended) + { + /* NaN and Inf are encoded in the high-order double value + only. The low-order value is not significant. */ + type = double_type_node; + mode = DFmode; + arg = fold_build1_loc (loc, NOP_EXPR, type, arg); + } + get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); + real_from_string (&r, buf); + result = build_call_expr (isgr_fn, 2, + fold_build1_loc (loc, ABS_EXPR, type, arg), + build_real (type, r)); + return result; + } CASE_FLT_FN (BUILT_IN_FINITE): - case BUILT_IN_FINITED32: - case BUILT_IN_FINITED64: - case BUILT_IN_FINITED128: case BUILT_IN_ISFINITE: { - tree ret = fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISFINITE); - if (ret) - return ret; - return fold_builtin_interclass_mathfn (loc, fndecl, arg0); - } + /* isfinite(x) -> islessequal(fabs(x),DBL_MAX). */ + tree const isle_fn = builtin_decl_explicit (BUILT_IN_ISLESSEQUAL); + tree type = TREE_TYPE (arg); + REAL_VALUE_TYPE r; + char buf[128]; - CASE_FLT_FN (BUILT_IN_ISINF): - case BUILT_IN_ISINFD32: - case BUILT_IN_ISINFD64: - case BUILT_IN_ISINFD128: - { - tree ret = fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISINF); - if (ret) - return ret; - return fold_builtin_interclass_mathfn (loc, fndecl, arg0); + if (is_ibm_extended) + { + /* NaN and Inf are encoded in the high-order double value + only. The low-order value is not significant. */ + type = double_type_node; + mode = DFmode; + arg = fold_build1_loc (loc, NOP_EXPR, type, arg); + } + get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); + real_from_string (&r, buf); + result = build_call_expr (isle_fn, 2, + fold_build1_loc (loc, ABS_EXPR, type, arg), + build_real (type, r)); + /*result = fold_build2_loc (loc, UNGT_EXPR, + TREE_TYPE (TREE_TYPE (fndecl)), + fold_build1_loc (loc, ABS_EXPR, type, arg), + build_real (type, r)); + result = fold_build1_loc (loc, TRUTH_NOT_EXPR, + TREE_TYPE (TREE_TYPE (fndecl)), + result);*/ + return result; } - case BUILT_IN_ISNORMAL: - return fold_builtin_interclass_mathfn (loc, fndecl, arg0); - - case BUILT_IN_ISINF_SIGN: - return fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISINF_SIGN); + { + /* isnormal(x) -> isgreaterequal(fabs(x),DBL_MIN) & + islessequal(fabs(x),DBL_MAX). */ + tree const isle_fn = builtin_decl_explicit (BUILT_IN_ISLESSEQUAL); + tree type = TREE_TYPE (arg); + tree orig_arg, max_exp, min_exp; + machine_mode orig_mode = mode; + REAL_VALUE_TYPE rmax, rmin; + char buf[128]; - CASE_FLT_FN (BUILT_IN_ISNAN): - case BUILT_IN_ISNAND32: - case BUILT_IN_ISNAND64: - case BUILT_IN_ISNAND128: - return fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISNAN); + orig_arg = arg = builtin_save_expr (arg); + if (is_ibm_extended) + { + /* Use double to test the normal range of IBM extended + precision. Emin for IBM extended precision is + different to emin for IEEE double, being 53 higher + since the low double exponent is at least 53 lower + than the high double exponent. */ + type = double_type_node; + mode = DFmode; + arg = fold_build1_loc (loc, NOP_EXPR, type, arg); + } + arg = fold_build1_loc (loc, ABS_EXPR, type, arg); - case BUILT_IN_FREE: - if (integer_zerop (arg0)) - return build_empty_stmt (loc); - break; + get_max_float (REAL_MODE_FORMAT (mode), buf, sizeof (buf), false); + real_from_string (&rmax, buf); + sprintf (buf, "0x1p%d", REAL_MODE_FORMAT (orig_mode)->emin - 1); + real_from_string (&rmin, buf); + max_exp = build_real (type, rmax); + min_exp = build_real (type, rmin); + max_exp = build_call_expr (isle_fn, 2, arg, max_exp); + if (is_ibm_extended) + { + /* Testing the high end of the range is done just using + the high double, using the same test as isfinite(). + For the subnormal end of the range we first test the + high double, then if its magnitude is equal to the + limit of 0x1p-969, we test whether the low double is + non-zero and opposite sign to the high double. */ + tree const islt_fn = builtin_decl_explicit (BUILT_IN_ISLESS); + tree const isgt_fn = builtin_decl_explicit (BUILT_IN_ISGREATER); + tree gt_min = build_call_expr (isgt_fn, 2, arg, min_exp); + tree eq_min = fold_build2 (EQ_EXPR, integer_type_node, + arg, min_exp); + tree as_complex = build1 (VIEW_CONVERT_EXPR, + complex_double_type_node, orig_arg); + tree hi_dbl = build1 (REALPART_EXPR, type, as_complex); + tree lo_dbl = build1 (IMAGPART_EXPR, type, as_complex); + tree zero = build_real (type, dconst0); + tree hilt = build_call_expr (islt_fn, 2, hi_dbl, zero); + tree lolt = build_call_expr (islt_fn, 2, lo_dbl, zero); + tree logt = build_call_expr (isgt_fn, 2, lo_dbl, zero); + tree ok_lo = fold_build1 (TRUTH_NOT_EXPR, integer_type_node, + fold_build3 (COND_EXPR, + integer_type_node, + hilt, logt, lolt)); + eq_min = fold_build2 (TRUTH_ANDIF_EXPR, integer_type_node, + eq_min, ok_lo); + min_exp = fold_build2 (TRUTH_ORIF_EXPR, integer_type_node, + gt_min, eq_min); + } + else + { + tree const isge_fn + = builtin_decl_explicit (BUILT_IN_ISGREATEREQUAL); + min_exp = build_call_expr (isge_fn, 2, arg, min_exp); + } + result = fold_build2 (BIT_AND_EXPR, integer_type_node, + max_exp, min_exp); + return result; + } default: break; } return NULL_TREE; - } -/* Folds a call EXPR (which may be null) to built-in function FNDECL - with 2 arguments, ARG0 and ARG1. This function returns NULL_TREE - if no simplification was possible. */ +/* Fold a call to __builtin_isnan(), __builtin_isinf, __builtin_finite. + ARG is the argument for the call. */ static tree -fold_builtin_2 (location_t loc, tree expr, tree fndecl, tree arg0, tree arg1) +fold_builtin_classify (location_t loc, tree fndecl, tree arg, int builtin_index) { tree type = TREE_TYPE (TREE_TYPE (fndecl)); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - if (TREE_CODE (arg0) == ERROR_MARK - || TREE_CODE (arg1) == ERROR_MARK) + if (!validate_arg (arg, REAL_TYPE)) return NULL_TREE; - if (tree ret = fold_const_call (as_combined_fn (fcode), type, arg0, arg1)) - return ret; - - switch (fcode) + switch (builtin_index) { - CASE_FLT_FN_REENT (BUILT_IN_GAMMA): /* GAMMA_R */ - CASE_FLT_FN_REENT (BUILT_IN_LGAMMA): /* LGAMMA_R */ - if (validate_arg (arg0, REAL_TYPE) - && validate_arg (arg1, POINTER_TYPE)) - return do_mpfr_lgamma_r (arg0, arg1, type); - break; + case BUILT_IN_ISINF: + if (tree_expr_infinite_p (arg)) + return omit_one_operand_loc (loc, type, integer_one_node, arg); + if (!tree_expr_maybe_infinite_p (arg)) + return omit_one_operand_loc (loc, type, integer_zero_node, arg); + return NULL_TREE; - CASE_FLT_FN (BUILT_IN_FREXP): - return fold_builtin_frexp (loc, arg0, arg1, type); + case BUILT_IN_ISINF_SIGN: + { + /* isinf_sign(x) -> isinf(x) ? (signbit(x) ? -1 : 1) : 0 */ + /* In a boolean context, GCC will fold the inner COND_EXPR to + 1. So e.g. "if (isinf_sign(x))" would be folded to just + "if (isinf(x) ? 1 : 0)" which becomes "if (isinf(x))". */ + tree signbit_fn = builtin_decl_explicit (BUILT_IN_SIGNBIT); + tree isinf_fn = builtin_decl_explicit (BUILT_IN_ISINF); + tree tmp = NULL_TREE; - CASE_FLT_FN (BUILT_IN_MODF): - return fold_builtin_modf (loc, arg0, arg1, type); + arg = builtin_save_expr (arg); - case BUILT_IN_STRSPN: - return fold_builtin_strspn (loc, expr, arg0, arg1); + if (signbit_fn && isinf_fn) + { + tree signbit_call = build_call_expr_loc (loc, signbit_fn, 1, arg); + tree isinf_call = build_call_expr_loc (loc, isinf_fn, 1, arg); + + signbit_call = fold_build2_loc (loc, NE_EXPR, integer_type_node, + signbit_call, integer_zero_node); + isinf_call = fold_build2_loc (loc, NE_EXPR, integer_type_node, + isinf_call, integer_zero_node); + + tmp = fold_build3_loc (loc, COND_EXPR, integer_type_node, signbit_call, + integer_minus_one_node, integer_one_node); + tmp = fold_build3_loc (loc, COND_EXPR, integer_type_node, + isinf_call, tmp, + integer_zero_node); + } + + return tmp; + } + + case BUILT_IN_ISFINITE: + if (tree_expr_finite_p (arg)) + return omit_one_operand_loc (loc, type, integer_one_node, arg); + if (tree_expr_nan_p (arg) || tree_expr_infinite_p (arg)) + return omit_one_operand_loc (loc, type, integer_zero_node, arg); + return NULL_TREE; + + case BUILT_IN_ISNAN: + if (tree_expr_nan_p (arg)) + return omit_one_operand_loc (loc, type, integer_one_node, arg); + if (!tree_expr_maybe_nan_p (arg)) + return omit_one_operand_loc (loc, type, integer_zero_node, arg); + + { + bool is_ibm_extended = MODE_COMPOSITE_P (TYPE_MODE (TREE_TYPE (arg))); + if (is_ibm_extended) + { + /* NaN and Inf are encoded in the high-order double value + only. The low-order value is not significant. */ + arg = fold_build1_loc (loc, NOP_EXPR, double_type_node, arg); + } + } + arg = builtin_save_expr (arg); + return fold_build2_loc (loc, UNORDERED_EXPR, type, arg, arg); + + default: + gcc_unreachable (); + } +} - case BUILT_IN_STRCSPN: - return fold_builtin_strcspn (loc, expr, arg0, arg1); +/* Fold a call to __builtin_fpclassify(int, int, int, int, int, ...). + This builtin will generate code to return the appropriate floating + point classification depending on the value of the floating point + number passed in. The possible return values must be supplied as + int arguments to the call in the following order: FP_NAN, FP_INFINITE, + FP_NORMAL, FP_SUBNORMAL and FP_ZERO. The ellipses is for exactly + one floating point argument which is "type generic". */ - case BUILT_IN_STRPBRK: - return fold_builtin_strpbrk (loc, expr, arg0, arg1, type); +static tree +fold_builtin_fpclassify (location_t loc, tree *args, int nargs) +{ + tree fp_nan, fp_infinite, fp_normal, fp_subnormal, fp_zero, + arg, type, res, tmp; + machine_mode mode; + REAL_VALUE_TYPE r; + char buf[128]; - case BUILT_IN_EXPECT: - return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, NULL_TREE); + /* Verify the required arguments in the original call. */ + if (nargs != 6 + || !validate_arg (args[0], INTEGER_TYPE) + || !validate_arg (args[1], INTEGER_TYPE) + || !validate_arg (args[2], INTEGER_TYPE) + || !validate_arg (args[3], INTEGER_TYPE) + || !validate_arg (args[4], INTEGER_TYPE) + || !validate_arg (args[5], REAL_TYPE)) + return NULL_TREE; - case BUILT_IN_ISGREATER: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNLE_EXPR, LE_EXPR); - case BUILT_IN_ISGREATEREQUAL: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNLT_EXPR, LT_EXPR); - case BUILT_IN_ISLESS: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNGE_EXPR, GE_EXPR); - case BUILT_IN_ISLESSEQUAL: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNGT_EXPR, GT_EXPR); - case BUILT_IN_ISLESSGREATER: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNEQ_EXPR, EQ_EXPR); - case BUILT_IN_ISUNORDERED: - return fold_builtin_unordered_cmp (loc, fndecl, - arg0, arg1, UNORDERED_EXPR, - NOP_EXPR); + fp_nan = args[0]; + fp_infinite = args[1]; + fp_normal = args[2]; + fp_subnormal = args[3]; + fp_zero = args[4]; + arg = args[5]; + type = TREE_TYPE (arg); + mode = TYPE_MODE (type); + arg = builtin_save_expr (fold_build1_loc (loc, ABS_EXPR, type, arg)); - /* We do the folding for va_start in the expander. */ - case BUILT_IN_VA_START: - break; + /* fpclassify(x) -> + isnan(x) ? FP_NAN : + (fabs(x) == Inf ? FP_INFINITE : + (fabs(x) >= DBL_MIN ? FP_NORMAL : + (x == 0 ? FP_ZERO : FP_SUBNORMAL))). */ - case BUILT_IN_OBJECT_SIZE: - return fold_builtin_object_size (arg0, arg1); + tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, arg, + build_real (type, dconst0)); + res = fold_build3_loc (loc, COND_EXPR, integer_type_node, + tmp, fp_zero, fp_subnormal); - case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: - return fold_builtin_atomic_always_lock_free (arg0, arg1); + sprintf (buf, "0x1p%d", REAL_MODE_FORMAT (mode)->emin - 1); + real_from_string (&r, buf); + tmp = fold_build2_loc (loc, GE_EXPR, integer_type_node, + arg, build_real (type, r)); + res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, fp_normal, res); - case BUILT_IN_ATOMIC_IS_LOCK_FREE: - return fold_builtin_atomic_is_lock_free (arg0, arg1); + if (tree_expr_maybe_infinite_p (arg)) + { + real_inf (&r); + tmp = fold_build2_loc (loc, EQ_EXPR, integer_type_node, arg, + build_real (type, r)); + res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, + fp_infinite, res); + } - default: - break; + if (tree_expr_maybe_nan_p (arg)) + { + tmp = fold_build2_loc (loc, ORDERED_EXPR, integer_type_node, arg, arg); + res = fold_build3_loc (loc, COND_EXPR, integer_type_node, tmp, res, fp_nan); } - return NULL_TREE; + + return res; } -/* Fold a call to built-in function FNDECL with 3 arguments, ARG0, ARG1, - and ARG2. - This function returns NULL_TREE if no simplification was possible. */ +/* Fold a call to an unordered comparison function such as + __builtin_isgreater(). FNDECL is the FUNCTION_DECL for the function + being called and ARG0 and ARG1 are the arguments for the call. + UNORDERED_CODE and ORDERED_CODE are comparison codes that give + the opposite of the desired result. UNORDERED_CODE is used + for modes that can hold NaNs and ORDERED_CODE is used for + the rest. */ static tree -fold_builtin_3 (location_t loc, tree fndecl, - tree arg0, tree arg1, tree arg2) +fold_builtin_unordered_cmp (location_t loc, tree fndecl, tree arg0, tree arg1, + enum tree_code unordered_code, + enum tree_code ordered_code) { tree type = TREE_TYPE (TREE_TYPE (fndecl)); - enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + enum tree_code code; + tree type0, type1; + enum tree_code code0, code1; + tree cmp_type = NULL_TREE; - if (TREE_CODE (arg0) == ERROR_MARK - || TREE_CODE (arg1) == ERROR_MARK - || TREE_CODE (arg2) == ERROR_MARK) - return NULL_TREE; + type0 = TREE_TYPE (arg0); + type1 = TREE_TYPE (arg1); - if (tree ret = fold_const_call (as_combined_fn (fcode), type, - arg0, arg1, arg2)) - return ret; + code0 = TREE_CODE (type0); + code1 = TREE_CODE (type1); - switch (fcode) - { + if (code0 == REAL_TYPE && code1 == REAL_TYPE) + /* Choose the wider of two real types. */ + cmp_type = TYPE_PRECISION (type0) >= TYPE_PRECISION (type1) + ? type0 : type1; + else if (code0 == REAL_TYPE && code1 == INTEGER_TYPE) + cmp_type = type0; + else if (code0 == INTEGER_TYPE && code1 == REAL_TYPE) + cmp_type = type1; - CASE_FLT_FN (BUILT_IN_SINCOS): - return fold_builtin_sincos (loc, arg0, arg1, arg2); + arg0 = fold_convert_loc (loc, cmp_type, arg0); + arg1 = fold_convert_loc (loc, cmp_type, arg1); - CASE_FLT_FN (BUILT_IN_REMQUO): - if (validate_arg (arg0, REAL_TYPE) - && validate_arg (arg1, REAL_TYPE) - && validate_arg (arg2, POINTER_TYPE)) - return do_mpfr_remquo (arg0, arg1, arg2); - break; + if (unordered_code == UNORDERED_EXPR) + { + if (tree_expr_nan_p (arg0) || tree_expr_nan_p (arg1)) + return omit_two_operands_loc (loc, type, integer_one_node, arg0, arg1); + if (!tree_expr_maybe_nan_p (arg0) && !tree_expr_maybe_nan_p (arg1)) + return omit_two_operands_loc (loc, type, integer_zero_node, arg0, arg1); + return fold_build2_loc (loc, UNORDERED_EXPR, type, arg0, arg1); + } - case BUILT_IN_MEMCMP: - return fold_builtin_memcmp (loc, arg0, arg1, arg2); + code = (tree_expr_maybe_nan_p (arg0) || tree_expr_maybe_nan_p (arg1)) + ? unordered_code : ordered_code; + return fold_build1_loc (loc, TRUTH_NOT_EXPR, type, + fold_build2_loc (loc, code, type, arg0, arg1)); +} - case BUILT_IN_EXPECT: - return fold_builtin_expect (loc, arg0, arg1, arg2, NULL_TREE); +/* Fold __builtin_{,s,u}{add,sub,mul}{,l,ll}_overflow, either into normal + arithmetics if it can never overflow, or into internal functions that + return both result of arithmetics and overflowed boolean flag in + a complex integer result, or some other check for overflow. + Similarly fold __builtin_{add,sub,mul}_overflow_p to just the overflow + checking part of that. */ - case BUILT_IN_EXPECT_WITH_PROBABILITY: - return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, arg2); +static tree +fold_builtin_arith_overflow (location_t loc, enum built_in_function fcode, + tree arg0, tree arg1, tree arg2) +{ + enum internal_fn ifn = IFN_LAST; + /* The code of the expression corresponding to the built-in. */ + enum tree_code opcode = ERROR_MARK; + bool ovf_only = false; - case BUILT_IN_ADD_OVERFLOW: - case BUILT_IN_SUB_OVERFLOW: - case BUILT_IN_MUL_OVERFLOW: + switch (fcode) + { case BUILT_IN_ADD_OVERFLOW_P: - case BUILT_IN_SUB_OVERFLOW_P: - case BUILT_IN_MUL_OVERFLOW_P: + ovf_only = true; + /* FALLTHRU */ + case BUILT_IN_ADD_OVERFLOW: case BUILT_IN_SADD_OVERFLOW: case BUILT_IN_SADDL_OVERFLOW: case BUILT_IN_SADDLL_OVERFLOW: - case BUILT_IN_SSUB_OVERFLOW: - case BUILT_IN_SSUBL_OVERFLOW: - case BUILT_IN_SSUBLL_OVERFLOW: - case BUILT_IN_SMUL_OVERFLOW: - case BUILT_IN_SMULL_OVERFLOW: - case BUILT_IN_SMULLL_OVERFLOW: case BUILT_IN_UADD_OVERFLOW: case BUILT_IN_UADDL_OVERFLOW: case BUILT_IN_UADDLL_OVERFLOW: + opcode = PLUS_EXPR; + ifn = IFN_ADD_OVERFLOW; + break; + case BUILT_IN_SUB_OVERFLOW_P: + ovf_only = true; + /* FALLTHRU */ + case BUILT_IN_SUB_OVERFLOW: + case BUILT_IN_SSUB_OVERFLOW: + case BUILT_IN_SSUBL_OVERFLOW: + case BUILT_IN_SSUBLL_OVERFLOW: case BUILT_IN_USUB_OVERFLOW: case BUILT_IN_USUBL_OVERFLOW: case BUILT_IN_USUBLL_OVERFLOW: + opcode = MINUS_EXPR; + ifn = IFN_SUB_OVERFLOW; + break; + case BUILT_IN_MUL_OVERFLOW_P: + ovf_only = true; + /* FALLTHRU */ + case BUILT_IN_MUL_OVERFLOW: + case BUILT_IN_SMUL_OVERFLOW: + case BUILT_IN_SMULL_OVERFLOW: + case BUILT_IN_SMULLL_OVERFLOW: case BUILT_IN_UMUL_OVERFLOW: case BUILT_IN_UMULL_OVERFLOW: case BUILT_IN_UMULLL_OVERFLOW: - return fold_builtin_arith_overflow (loc, fcode, arg0, arg1, arg2); - - default: + opcode = MULT_EXPR; + ifn = IFN_MUL_OVERFLOW; break; + default: + gcc_unreachable (); + } + + /* For the "generic" overloads, the first two arguments can have different + types and the last argument determines the target type to use to check + for overflow. The arguments of the other overloads all have the same + type. */ + tree type = ovf_only ? TREE_TYPE (arg2) : TREE_TYPE (TREE_TYPE (arg2)); + + /* For the __builtin_{add,sub,mul}_overflow_p builtins, when the first two + arguments are constant, attempt to fold the built-in call into a constant + expression indicating whether or not it detected an overflow. */ + if (ovf_only + && TREE_CODE (arg0) == INTEGER_CST + && TREE_CODE (arg1) == INTEGER_CST) + /* Perform the computation in the target type and check for overflow. */ + return omit_one_operand_loc (loc, boolean_type_node, + arith_overflowed_p (opcode, type, arg0, arg1) + ? boolean_true_node : boolean_false_node, + arg2); + + tree intres, ovfres; + if (TREE_CODE (arg0) == INTEGER_CST && TREE_CODE (arg1) == INTEGER_CST) + { + intres = fold_binary_loc (loc, opcode, type, + fold_convert_loc (loc, type, arg0), + fold_convert_loc (loc, type, arg1)); + if (TREE_OVERFLOW (intres)) + intres = drop_tree_overflow (intres); + ovfres = (arith_overflowed_p (opcode, type, arg0, arg1) + ? boolean_true_node : boolean_false_node); } - return NULL_TREE; + else + { + tree ctype = build_complex_type (type); + tree call = build_call_expr_internal_loc (loc, ifn, ctype, 2, + arg0, arg1); + tree tgt = save_expr (call); + intres = build1_loc (loc, REALPART_EXPR, type, tgt); + ovfres = build1_loc (loc, IMAGPART_EXPR, type, tgt); + ovfres = fold_convert_loc (loc, boolean_type_node, ovfres); + } + + if (ovf_only) + return omit_one_operand_loc (loc, boolean_type_node, ovfres, arg2); + + tree mem_arg2 = build_fold_indirect_ref_loc (loc, arg2); + tree store + = fold_build2_loc (loc, MODIFY_EXPR, void_type_node, mem_arg2, intres); + return build2_loc (loc, COMPOUND_EXPR, boolean_type_node, store, ovfres); } -/* Folds a call EXPR (which may be null) to built-in function FNDECL. - ARGS is an array of NARGS arguments. IGNORE is true if the result - of the function call is ignored. This function returns NULL_TREE - if no simplification was possible. */ +/* Fold a call to __builtin_FILE to a constant string. */ -static tree -fold_builtin_n (location_t loc, tree expr, tree fndecl, tree *args, - int nargs, bool) +static inline tree +fold_builtin_FILE (location_t loc) { - tree ret = NULL_TREE; - - switch (nargs) - { - case 0: - ret = fold_builtin_0 (loc, fndecl); - break; - case 1: - ret = fold_builtin_1 (loc, expr, fndecl, args[0]); - break; - case 2: - ret = fold_builtin_2 (loc, expr, fndecl, args[0], args[1]); - break; - case 3: - ret = fold_builtin_3 (loc, fndecl, args[0], args[1], args[2]); - break; - default: - ret = fold_builtin_varargs (loc, fndecl, args, nargs); - break; - } - if (ret) + if (const char *fname = LOCATION_FILE (loc)) { - ret = build1 (NOP_EXPR, TREE_TYPE (ret), ret); - SET_EXPR_LOCATION (ret, loc); - return ret; + /* The documentation says this builtin is equivalent to the preprocessor + __FILE__ macro so it appears appropriate to use the same file prefix + mappings. */ + fname = remap_macro_filename (fname); + return build_string_literal (strlen (fname) + 1, fname); } - return NULL_TREE; + + return build_string_literal (1, ""); } -/* Construct a new CALL_EXPR to FNDECL using the tail of the argument - list ARGS along with N new arguments in NEWARGS. SKIP is the number - of arguments in ARGS to be omitted. OLDNARGS is the number of - elements in ARGS. */ +/* Fold a call to __builtin_FUNCTION to a constant string. */ -static tree -rewrite_call_expr_valist (location_t loc, int oldnargs, tree *args, - int skip, tree fndecl, int n, va_list newargs) +static inline tree +fold_builtin_FUNCTION () { - int nargs = oldnargs - skip + n; - tree *buffer; - - if (n > 0) - { - int i, j; + const char *name = ""; - buffer = XALLOCAVEC (tree, nargs); - for (i = 0; i < n; i++) - buffer[i] = va_arg (newargs, tree); - for (j = skip; j < oldnargs; j++, i++) - buffer[i] = args[j]; - } - else - buffer = args + skip; + if (current_function_decl) + name = lang_hooks.decl_printable_name (current_function_decl, 0); - return build_call_expr_loc_array (loc, fndecl, nargs, buffer); + return build_string_literal (strlen (name) + 1, name); } -/* Return true if FNDECL shouldn't be folded right now. - If a built-in function has an inline attribute always_inline - wrapper, defer folding it after always_inline functions have - been inlined, otherwise e.g. -D_FORTIFY_SOURCE checking - might not be performed. */ +/* Fold a call to __builtin_LINE to an integer constant. */ -bool -avoid_folding_inline_builtin (tree fndecl) +static inline tree +fold_builtin_LINE (location_t loc, tree type) { - return (DECL_DECLARED_INLINE_P (fndecl) - && DECL_DISREGARD_INLINE_LIMITS (fndecl) - && cfun - && !cfun->always_inline_functions_inlined - && lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl))); + return build_int_cst (type, LOCATION_LINE (loc)); } -/* A wrapper function for builtin folding that prevents warnings for - "statement without effect" and the like, caused by removing the - call node earlier than the warning is generated. */ +/* Fold a call to built-in function FNDECL with 0 arguments. + This function returns NULL_TREE if no simplification was possible. */ -tree -fold_call_expr (location_t loc, tree exp, bool ignore) +static tree +fold_builtin_0 (location_t loc, tree fndecl) { - tree ret = NULL_TREE; - tree fndecl = get_callee_fndecl (exp); - if (fndecl && fndecl_built_in_p (fndecl) - /* If CALL_EXPR_VA_ARG_PACK is set, the arguments aren't finalized - yet. Defer folding until we see all the arguments - (after inlining). */ - && !CALL_EXPR_VA_ARG_PACK (exp)) + tree type = TREE_TYPE (TREE_TYPE (fndecl)); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); + switch (fcode) { - int nargs = call_expr_nargs (exp); + case BUILT_IN_FILE: + return fold_builtin_FILE (loc); - /* Before gimplification CALL_EXPR_VA_ARG_PACK is not set, but - instead last argument is __builtin_va_arg_pack (). Defer folding - even in that case, until arguments are finalized. */ - if (nargs && TREE_CODE (CALL_EXPR_ARG (exp, nargs - 1)) == CALL_EXPR) - { - tree fndecl2 = get_callee_fndecl (CALL_EXPR_ARG (exp, nargs - 1)); - if (fndecl2 && fndecl_built_in_p (fndecl2, BUILT_IN_VA_ARG_PACK)) - return NULL_TREE; - } + case BUILT_IN_FUNCTION: + return fold_builtin_FUNCTION (); - if (avoid_folding_inline_builtin (fndecl)) - return NULL_TREE; + case BUILT_IN_LINE: + return fold_builtin_LINE (loc, type); - if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) - return targetm.fold_builtin (fndecl, call_expr_nargs (exp), - CALL_EXPR_ARGP (exp), ignore); - else - { - tree *args = CALL_EXPR_ARGP (exp); - ret = fold_builtin_n (loc, exp, fndecl, args, nargs, ignore); - if (ret) - return ret; - } - } - return NULL_TREE; -} + CASE_FLT_FN (BUILT_IN_INF): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_INF): + case BUILT_IN_INFD32: + case BUILT_IN_INFD64: + case BUILT_IN_INFD128: + return fold_builtin_inf (loc, type, true); -/* Fold a CALL_EXPR with type TYPE with FN as the function expression. - N arguments are passed in the array ARGARRAY. Return a folded - expression or NULL_TREE if no simplification was possible. */ + CASE_FLT_FN (BUILT_IN_HUGE_VAL): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_HUGE_VAL): + return fold_builtin_inf (loc, type, false); -tree -fold_builtin_call_array (location_t loc, tree, - tree fn, - int n, - tree *argarray) -{ - if (TREE_CODE (fn) != ADDR_EXPR) - return NULL_TREE; + case BUILT_IN_CLASSIFY_TYPE: + return fold_builtin_classify_type (NULL_TREE); - tree fndecl = TREE_OPERAND (fn, 0); - if (TREE_CODE (fndecl) == FUNCTION_DECL - && fndecl_built_in_p (fndecl)) - { - /* If last argument is __builtin_va_arg_pack (), arguments to this - function are not finalized yet. Defer folding until they are. */ - if (n && TREE_CODE (argarray[n - 1]) == CALL_EXPR) - { - tree fndecl2 = get_callee_fndecl (argarray[n - 1]); - if (fndecl2 && fndecl_built_in_p (fndecl2, BUILT_IN_VA_ARG_PACK)) - return NULL_TREE; - } - if (avoid_folding_inline_builtin (fndecl)) - return NULL_TREE; - if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) - return targetm.fold_builtin (fndecl, n, argarray, false); - else - return fold_builtin_n (loc, NULL_TREE, fndecl, argarray, n, false); + default: + break; } - return NULL_TREE; } -/* Construct a new CALL_EXPR using the tail of the argument list of EXP - along with N new arguments specified as the "..." parameters. SKIP - is the number of arguments in EXP to be omitted. This function is used - to do varargs-to-varargs transformations. */ +/* Fold a call to built-in function FNDECL with 1 argument, ARG0. + This function returns NULL_TREE if no simplification was possible. */ static tree -rewrite_call_expr (location_t loc, tree exp, int skip, tree fndecl, int n, ...) +fold_builtin_1 (location_t loc, tree expr, tree fndecl, tree arg0) { - va_list ap; - tree t; + tree type = TREE_TYPE (TREE_TYPE (fndecl)); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - va_start (ap, n); - t = rewrite_call_expr_valist (loc, call_expr_nargs (exp), - CALL_EXPR_ARGP (exp), skip, fndecl, n, ap); - va_end (ap); + if (TREE_CODE (arg0) == ERROR_MARK) + return NULL_TREE; - return t; -} + if (tree ret = fold_const_call (as_combined_fn (fcode), type, arg0)) + return ret; -/* Validate a single argument ARG against a tree code CODE representing - a type. Return true when argument is valid. */ + switch (fcode) + { + case BUILT_IN_CONSTANT_P: + { + tree val = fold_builtin_constant_p (arg0); -static bool -validate_arg (const_tree arg, enum tree_code code) -{ - if (!arg) - return false; - else if (code == POINTER_TYPE) - return POINTER_TYPE_P (TREE_TYPE (arg)); - else if (code == INTEGER_TYPE) - return INTEGRAL_TYPE_P (TREE_TYPE (arg)); - return code == TREE_CODE (TREE_TYPE (arg)); -} + /* Gimplification will pull the CALL_EXPR for the builtin out of + an if condition. When not optimizing, we'll not CSE it back. + To avoid link error types of regressions, return false now. */ + if (!val && !optimize) + val = integer_zero_node; + + return val; + } + + case BUILT_IN_CLASSIFY_TYPE: + return fold_builtin_classify_type (arg0); + + case BUILT_IN_STRLEN: + return fold_builtin_strlen (loc, expr, type, arg0); + + CASE_FLT_FN (BUILT_IN_FABS): + CASE_FLT_FN_FLOATN_NX (BUILT_IN_FABS): + case BUILT_IN_FABSD32: + case BUILT_IN_FABSD64: + case BUILT_IN_FABSD128: + return fold_builtin_fabs (loc, arg0, type); -/* 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 ellipses, otherwise the last specifier must be a - VOID_TYPE. + case BUILT_IN_ABS: + case BUILT_IN_LABS: + case BUILT_IN_LLABS: + case BUILT_IN_IMAXABS: + return fold_builtin_abs (loc, arg0, type); - This is the GIMPLE version of validate_arglist. Eventually we want to - completely convert builtins.c to work from GIMPLEs and the tree based - validate_arglist will then be removed. */ + CASE_FLT_FN (BUILT_IN_CONJ): + if (validate_arg (arg0, COMPLEX_TYPE) + && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) + return fold_build1_loc (loc, CONJ_EXPR, type, arg0); + break; -bool -validate_gimple_arglist (const gcall *call, ...) -{ - enum tree_code code; - bool res = 0; - va_list ap; - const_tree arg; - size_t i; + CASE_FLT_FN (BUILT_IN_CREAL): + if (validate_arg (arg0, COMPLEX_TYPE) + && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) + return non_lvalue_loc (loc, fold_build1_loc (loc, REALPART_EXPR, type, arg0)); + break; - va_start (ap, call); - i = 0; + CASE_FLT_FN (BUILT_IN_CIMAG): + if (validate_arg (arg0, COMPLEX_TYPE) + && TREE_CODE (TREE_TYPE (TREE_TYPE (arg0))) == REAL_TYPE) + return non_lvalue_loc (loc, fold_build1_loc (loc, IMAGPART_EXPR, type, arg0)); + break; - do - { - 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 = (i == gimple_call_num_args (call)); - goto end; - 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 = gimple_call_arg (call, i++); - if (!validate_arg (arg, code)) - goto end; - break; - } - } - while (1); + CASE_FLT_FN (BUILT_IN_CARG): + return fold_builtin_carg (loc, arg0, type); - /* We need gotos here since we can only have one VA_CLOSE in a - function. */ - end: ; - va_end (ap); + case BUILT_IN_ISASCII: + return fold_builtin_isascii (loc, arg0); - return res; -} + case BUILT_IN_TOASCII: + return fold_builtin_toascii (loc, arg0); -/* Default target-specific builtin expander that does nothing. */ + case BUILT_IN_ISDIGIT: + return fold_builtin_isdigit (loc, arg0); -rtx -default_expand_builtin (tree exp ATTRIBUTE_UNUSED, - rtx target ATTRIBUTE_UNUSED, - rtx subtarget ATTRIBUTE_UNUSED, - machine_mode mode ATTRIBUTE_UNUSED, - int ignore ATTRIBUTE_UNUSED) -{ - return NULL_RTX; -} + CASE_FLT_FN (BUILT_IN_FINITE): + case BUILT_IN_FINITED32: + case BUILT_IN_FINITED64: + case BUILT_IN_FINITED128: + case BUILT_IN_ISFINITE: + { + tree ret = fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISFINITE); + if (ret) + return ret; + return fold_builtin_interclass_mathfn (loc, fndecl, arg0); + } -/* Returns true is EXP represents data that would potentially reside - in a readonly section. */ + CASE_FLT_FN (BUILT_IN_ISINF): + case BUILT_IN_ISINFD32: + case BUILT_IN_ISINFD64: + case BUILT_IN_ISINFD128: + { + tree ret = fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISINF); + if (ret) + return ret; + return fold_builtin_interclass_mathfn (loc, fndecl, arg0); + } -bool -readonly_data_expr (tree exp) -{ - STRIP_NOPS (exp); + case BUILT_IN_ISNORMAL: + return fold_builtin_interclass_mathfn (loc, fndecl, arg0); - if (TREE_CODE (exp) != ADDR_EXPR) - return false; + case BUILT_IN_ISINF_SIGN: + return fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISINF_SIGN); - exp = get_base_address (TREE_OPERAND (exp, 0)); - if (!exp) - return false; + CASE_FLT_FN (BUILT_IN_ISNAN): + case BUILT_IN_ISNAND32: + case BUILT_IN_ISNAND64: + case BUILT_IN_ISNAND128: + return fold_builtin_classify (loc, fndecl, arg0, BUILT_IN_ISNAN); - /* Make sure we call decl_readonly_section only for trees it - can handle (since it returns true for everything it doesn't - understand). */ - if (TREE_CODE (exp) == STRING_CST - || TREE_CODE (exp) == CONSTRUCTOR - || (VAR_P (exp) && TREE_STATIC (exp))) - return decl_readonly_section (exp, 0); - else - return false; -} + case BUILT_IN_FREE: + if (integer_zerop (arg0)) + return build_empty_stmt (loc); + break; -/* Simplify a call to the strpbrk builtin. S1 and S2 are the arguments - to the call, and TYPE is its return type. + default: + break; + } - Return NULL_TREE if no simplification was possible, otherwise return the - simplified form of the call as a tree. + return NULL_TREE; - The simplified form may be a constant or other expression which - computes the same value, but in a more efficient manner (including - calls to other builtin functions). +} - The call may contain arguments which need to be evaluated, but - which are not useful to determine the result of the call. In - this case we return a chain of COMPOUND_EXPRs. The LHS of each - COMPOUND_EXPR will be an argument which must be evaluated. - COMPOUND_EXPRs are chained through their RHS. The RHS of the last - COMPOUND_EXPR in the chain will contain the tree for the simplified - form of the builtin function call. */ +/* Folds a call EXPR (which may be null) to built-in function FNDECL + with 2 arguments, ARG0 and ARG1. This function returns NULL_TREE + if no simplification was possible. */ static tree -fold_builtin_strpbrk (location_t loc, tree, tree s1, tree s2, tree type) +fold_builtin_2 (location_t loc, tree expr, tree fndecl, tree arg0, tree arg1) { - if (!validate_arg (s1, POINTER_TYPE) - || !validate_arg (s2, POINTER_TYPE)) - return NULL_TREE; - - tree fn; - const char *p1, *p2; + tree type = TREE_TYPE (TREE_TYPE (fndecl)); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - p2 = c_getstr (s2); - if (p2 == NULL) + if (TREE_CODE (arg0) == ERROR_MARK + || TREE_CODE (arg1) == ERROR_MARK) return NULL_TREE; - p1 = c_getstr (s1); - if (p1 != NULL) + if (tree ret = fold_const_call (as_combined_fn (fcode), type, arg0, arg1)) + return ret; + + switch (fcode) { - const char *r = strpbrk (p1, p2); - tree tem; + CASE_FLT_FN_REENT (BUILT_IN_GAMMA): /* GAMMA_R */ + CASE_FLT_FN_REENT (BUILT_IN_LGAMMA): /* LGAMMA_R */ + if (validate_arg (arg0, REAL_TYPE) + && validate_arg (arg1, POINTER_TYPE)) + return do_mpfr_lgamma_r (arg0, arg1, type); + break; - if (r == NULL) - return build_int_cst (TREE_TYPE (s1), 0); + CASE_FLT_FN (BUILT_IN_FREXP): + return fold_builtin_frexp (loc, arg0, arg1, type); - /* Return an offset into the constant string argument. */ - tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1); - return fold_convert_loc (loc, type, tem); - } + CASE_FLT_FN (BUILT_IN_MODF): + return fold_builtin_modf (loc, arg0, arg1, type); - if (p2[0] == '\0') - /* strpbrk(x, "") == NULL. - Evaluate and ignore s1 in case it had side-effects. */ - return omit_one_operand_loc (loc, type, integer_zero_node, s1); + case BUILT_IN_STRSPN: + return fold_builtin_strspn (loc, expr, arg0, arg1); - if (p2[1] != '\0') - return NULL_TREE; /* Really call strpbrk. */ + case BUILT_IN_STRCSPN: + return fold_builtin_strcspn (loc, expr, arg0, arg1); - fn = builtin_decl_implicit (BUILT_IN_STRCHR); - if (!fn) - return NULL_TREE; + case BUILT_IN_STRPBRK: + return fold_builtin_strpbrk (loc, expr, arg0, arg1, type); - /* New argument list transforming strpbrk(s1, s2) to - strchr(s1, s2[0]). */ - return build_call_expr_loc (loc, fn, 2, s1, - build_int_cst (integer_type_node, p2[0])); -} + case BUILT_IN_EXPECT: + return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, NULL_TREE); + + case BUILT_IN_ISGREATER: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNLE_EXPR, LE_EXPR); + case BUILT_IN_ISGREATEREQUAL: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNLT_EXPR, LT_EXPR); + case BUILT_IN_ISLESS: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNGE_EXPR, GE_EXPR); + case BUILT_IN_ISLESSEQUAL: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNGT_EXPR, GT_EXPR); + case BUILT_IN_ISLESSGREATER: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNEQ_EXPR, EQ_EXPR); + case BUILT_IN_ISUNORDERED: + return fold_builtin_unordered_cmp (loc, fndecl, + arg0, arg1, UNORDERED_EXPR, + NOP_EXPR); + + /* We do the folding for va_start in the expander. */ + case BUILT_IN_VA_START: + break; + + case BUILT_IN_OBJECT_SIZE: + return fold_builtin_object_size (arg0, arg1); -/* Simplify a call to the strspn builtin. S1 and S2 are the arguments - to the call. + case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: + return fold_builtin_atomic_always_lock_free (arg0, arg1); - Return NULL_TREE if no simplification was possible, otherwise return the - simplified form of the call as a tree. + case BUILT_IN_ATOMIC_IS_LOCK_FREE: + return fold_builtin_atomic_is_lock_free (arg0, arg1); - The simplified form may be a constant or other expression which - computes the same value, but in a more efficient manner (including - calls to other builtin functions). + default: + break; + } + return NULL_TREE; +} - The call may contain arguments which need to be evaluated, but - which are not useful to determine the result of the call. In - this case we return a chain of COMPOUND_EXPRs. The LHS of each - COMPOUND_EXPR will be an argument which must be evaluated. - COMPOUND_EXPRs are chained through their RHS. The RHS of the last - COMPOUND_EXPR in the chain will contain the tree for the simplified - form of the builtin function call. */ +/* Fold a call to built-in function FNDECL with 3 arguments, ARG0, ARG1, + and ARG2. + This function returns NULL_TREE if no simplification was possible. */ static tree -fold_builtin_strspn (location_t loc, tree expr, tree s1, tree s2) +fold_builtin_3 (location_t loc, tree fndecl, + tree arg0, tree arg1, tree arg2) { - if (!validate_arg (s1, POINTER_TYPE) - || !validate_arg (s2, POINTER_TYPE)) - return NULL_TREE; + tree type = TREE_TYPE (TREE_TYPE (fndecl)); + enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); - if (!check_nul_terminated_array (expr, s1) - || !check_nul_terminated_array (expr, s2)) + if (TREE_CODE (arg0) == ERROR_MARK + || TREE_CODE (arg1) == ERROR_MARK + || TREE_CODE (arg2) == ERROR_MARK) return NULL_TREE; - const char *p1 = c_getstr (s1), *p2 = c_getstr (s2); + if (tree ret = fold_const_call (as_combined_fn (fcode), type, + arg0, arg1, arg2)) + return ret; - /* If either argument is "", return NULL_TREE. */ - if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0')) - /* Evaluate and ignore both arguments in case either one has - side-effects. */ - return omit_two_operands_loc (loc, size_type_node, size_zero_node, - s1, s2); - return NULL_TREE; -} + switch (fcode) + { -/* Simplify a call to the strcspn builtin. S1 and S2 are the arguments - to the call. + CASE_FLT_FN (BUILT_IN_SINCOS): + return fold_builtin_sincos (loc, arg0, arg1, arg2); - Return NULL_TREE if no simplification was possible, otherwise return the - simplified form of the call as a tree. + CASE_FLT_FN (BUILT_IN_REMQUO): + if (validate_arg (arg0, REAL_TYPE) + && validate_arg (arg1, REAL_TYPE) + && validate_arg (arg2, POINTER_TYPE)) + return do_mpfr_remquo (arg0, arg1, arg2); + break; - The simplified form may be a constant or other expression which - computes the same value, but in a more efficient manner (including - calls to other builtin functions). + case BUILT_IN_MEMCMP: + return fold_builtin_memcmp (loc, arg0, arg1, arg2); - The call may contain arguments which need to be evaluated, but - which are not useful to determine the result of the call. In - this case we return a chain of COMPOUND_EXPRs. The LHS of each - COMPOUND_EXPR will be an argument which must be evaluated. - COMPOUND_EXPRs are chained through their RHS. The RHS of the last - COMPOUND_EXPR in the chain will contain the tree for the simplified - form of the builtin function call. */ + case BUILT_IN_EXPECT: + return fold_builtin_expect (loc, arg0, arg1, arg2, NULL_TREE); -static tree -fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2) -{ - if (!validate_arg (s1, POINTER_TYPE) - || !validate_arg (s2, POINTER_TYPE)) - return NULL_TREE; + case BUILT_IN_EXPECT_WITH_PROBABILITY: + return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, arg2); - if (!check_nul_terminated_array (expr, s1) - || !check_nul_terminated_array (expr, s2)) - return NULL_TREE; + case BUILT_IN_ADD_OVERFLOW: + case BUILT_IN_SUB_OVERFLOW: + case BUILT_IN_MUL_OVERFLOW: + case BUILT_IN_ADD_OVERFLOW_P: + case BUILT_IN_SUB_OVERFLOW_P: + case BUILT_IN_MUL_OVERFLOW_P: + case BUILT_IN_SADD_OVERFLOW: + case BUILT_IN_SADDL_OVERFLOW: + case BUILT_IN_SADDLL_OVERFLOW: + case BUILT_IN_SSUB_OVERFLOW: + case BUILT_IN_SSUBL_OVERFLOW: + case BUILT_IN_SSUBLL_OVERFLOW: + case BUILT_IN_SMUL_OVERFLOW: + case BUILT_IN_SMULL_OVERFLOW: + case BUILT_IN_SMULLL_OVERFLOW: + case BUILT_IN_UADD_OVERFLOW: + case BUILT_IN_UADDL_OVERFLOW: + case BUILT_IN_UADDLL_OVERFLOW: + case BUILT_IN_USUB_OVERFLOW: + case BUILT_IN_USUBL_OVERFLOW: + case BUILT_IN_USUBLL_OVERFLOW: + case BUILT_IN_UMUL_OVERFLOW: + case BUILT_IN_UMULL_OVERFLOW: + case BUILT_IN_UMULLL_OVERFLOW: + return fold_builtin_arith_overflow (loc, fcode, arg0, arg1, arg2); - /* If the first argument is "", return NULL_TREE. */ - const char *p1 = c_getstr (s1); - if (p1 && *p1 == '\0') - { - /* Evaluate and ignore argument s2 in case it has - side-effects. */ - return omit_one_operand_loc (loc, size_type_node, - size_zero_node, s2); + default: + break; } + return NULL_TREE; +} - /* If the second argument is "", return __builtin_strlen(s1). */ - const char *p2 = c_getstr (s2); - if (p2 && *p2 == '\0') - { - tree fn = builtin_decl_implicit (BUILT_IN_STRLEN); +/* Folds a call EXPR (which may be null) to built-in function FNDECL. + ARGS is an array of NARGS arguments. IGNORE is true if the result + of the function call is ignored. This function returns NULL_TREE + if no simplification was possible. */ - /* If the replacement _DECL isn't initialized, don't do the - transformation. */ - if (!fn) - return NULL_TREE; +static tree +fold_builtin_n (location_t loc, tree expr, tree fndecl, tree *args, + int nargs, bool) +{ + tree ret = NULL_TREE; - return build_call_expr_loc (loc, fn, 1, s1); + switch (nargs) + { + case 0: + ret = fold_builtin_0 (loc, fndecl); + break; + case 1: + ret = fold_builtin_1 (loc, expr, fndecl, args[0]); + break; + case 2: + ret = fold_builtin_2 (loc, expr, fndecl, args[0], args[1]); + break; + case 3: + ret = fold_builtin_3 (loc, fndecl, args[0], args[1], args[2]); + break; + default: + ret = fold_builtin_varargs (loc, fndecl, args, nargs); + break; + } + if (ret) + { + ret = build1 (NOP_EXPR, TREE_TYPE (ret), ret); + SET_EXPR_LOCATION (ret, loc); + return ret; } return NULL_TREE; } -/* Fold the next_arg or va_start call EXP. Returns true if there was an error - produced. False otherwise. This is done so that we don't output the error - or warning twice or three times. */ +/* Construct a new CALL_EXPR to FNDECL using the tail of the argument + list ARGS along with N new arguments in NEWARGS. SKIP is the number + of arguments in ARGS to be omitted. OLDNARGS is the number of + elements in ARGS. */ -bool -fold_builtin_next_arg (tree exp, bool va_start_p) +static tree +rewrite_call_expr_valist (location_t loc, int oldnargs, tree *args, + int skip, tree fndecl, int n, va_list newargs) { - tree fntype = TREE_TYPE (current_function_decl); - int nargs = call_expr_nargs (exp); - tree arg; - /* There is good chance the current input_location points inside the - definition of the va_start macro (perhaps on the token for - builtin) in a system header, so warnings will not be emitted. - Use the location in real source code. */ - location_t current_location = - linemap_unwind_to_first_non_reserved_loc (line_table, input_location, - NULL); + int nargs = oldnargs - skip + n; + tree *buffer; - if (!stdarg_p (fntype)) + if (n > 0) { - error ("% used in function with fixed arguments"); - return true; - } + int i, j; - if (va_start_p) - { - if (va_start_p && (nargs != 2)) - { - error ("wrong number of arguments to function %"); - return true; - } - arg = CALL_EXPR_ARG (exp, 1); + buffer = XALLOCAVEC (tree, nargs); + for (i = 0; i < n; i++) + buffer[i] = va_arg (newargs, tree); + for (j = skip; j < oldnargs; j++, i++) + buffer[i] = args[j]; } - /* We use __builtin_va_start (ap, 0, 0) or __builtin_next_arg (0, 0) - when we checked the arguments and if needed issued a warning. */ else - { - if (nargs == 0) - { - /* Evidently an out of date version of ; can't validate - va_start's second argument, but can still work as intended. */ - warning_at (current_location, - OPT_Wvarargs, - "%<__builtin_next_arg%> called without an argument"); - return true; - } - else if (nargs > 1) - { - error ("wrong number of arguments to function %<__builtin_next_arg%>"); - return true; - } - arg = CALL_EXPR_ARG (exp, 0); - } + buffer = args + skip; - if (TREE_CODE (arg) == SSA_NAME - && SSA_NAME_VAR (arg)) - arg = SSA_NAME_VAR (arg); + return build_call_expr_loc_array (loc, fndecl, nargs, buffer); +} + +/* Return true if FNDECL shouldn't be folded right now. + If a built-in function has an inline attribute always_inline + wrapper, defer folding it after always_inline functions have + been inlined, otherwise e.g. -D_FORTIFY_SOURCE checking + might not be performed. */ + +bool +avoid_folding_inline_builtin (tree fndecl) +{ + return (DECL_DECLARED_INLINE_P (fndecl) + && DECL_DISREGARD_INLINE_LIMITS (fndecl) + && cfun + && !cfun->always_inline_functions_inlined + && lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl))); +} + +/* A wrapper function for builtin folding that prevents warnings for + "statement without effect" and the like, caused by removing the + call node earlier than the warning is generated. */ - /* We destructively modify the call to be __builtin_va_start (ap, 0) - or __builtin_next_arg (0) the first time we see it, after checking - the arguments and if needed issuing a warning. */ - if (!integer_zerop (arg)) +tree +fold_call_expr (location_t loc, tree exp, bool ignore) +{ + tree ret = NULL_TREE; + tree fndecl = get_callee_fndecl (exp); + if (fndecl && fndecl_built_in_p (fndecl) + /* If CALL_EXPR_VA_ARG_PACK is set, the arguments aren't finalized + yet. Defer folding until we see all the arguments + (after inlining). */ + && !CALL_EXPR_VA_ARG_PACK (exp)) { - tree last_parm = tree_last (DECL_ARGUMENTS (current_function_decl)); + int nargs = call_expr_nargs (exp); - /* Strip off all nops for the sake of the comparison. This - is not quite the same as STRIP_NOPS. It does more. - We must also strip off INDIRECT_EXPR for C++ reference - parameters. */ - while (CONVERT_EXPR_P (arg) - || TREE_CODE (arg) == INDIRECT_REF) - arg = TREE_OPERAND (arg, 0); - if (arg != last_parm) + /* Before gimplification CALL_EXPR_VA_ARG_PACK is not set, but + instead last argument is __builtin_va_arg_pack (). Defer folding + even in that case, until arguments are finalized. */ + if (nargs && TREE_CODE (CALL_EXPR_ARG (exp, nargs - 1)) == CALL_EXPR) { - /* FIXME: Sometimes with the tree optimizers we can get the - not the last argument even though the user used the last - argument. We just warn and set the arg to be the last - argument so that we will get wrong-code because of - it. */ - warning_at (current_location, - OPT_Wvarargs, - "second parameter of % not last named argument"); + tree fndecl2 = get_callee_fndecl (CALL_EXPR_ARG (exp, nargs - 1)); + if (fndecl2 && fndecl_built_in_p (fndecl2, BUILT_IN_VA_ARG_PACK)) + return NULL_TREE; } - /* Undefined by C99 7.15.1.4p4 (va_start): - "If the parameter parmN is declared with the register storage - class, with a function or array type, or with a type that is - not compatible with the type that results after application of - the default argument promotions, the behavior is undefined." - */ - else if (DECL_REGISTER (arg)) - { - warning_at (current_location, - OPT_Wvarargs, - "undefined behavior when second parameter of " - "% is declared with % storage"); - } + if (avoid_folding_inline_builtin (fndecl)) + return NULL_TREE; - /* We want to verify the second parameter just once before the tree - optimizers are run and then avoid keeping it in the tree, - as otherwise we could warn even for correct code like: - void foo (int i, ...) - { va_list ap; i++; va_start (ap, i); va_end (ap); } */ - if (va_start_p) - CALL_EXPR_ARG (exp, 1) = integer_zero_node; + if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) + return targetm.fold_builtin (fndecl, call_expr_nargs (exp), + CALL_EXPR_ARGP (exp), ignore); else - CALL_EXPR_ARG (exp, 0) = integer_zero_node; + { + tree *args = CALL_EXPR_ARGP (exp); + ret = fold_builtin_n (loc, exp, fndecl, args, nargs, ignore); + if (ret) + return ret; + } } - return false; + return NULL_TREE; } +/* Fold a CALL_EXPR with type TYPE with FN as the function expression. + N arguments are passed in the array ARGARRAY. Return a folded + expression or NULL_TREE if no simplification was possible. */ -/* Expand a call EXP to __builtin_object_size. */ - -static rtx -expand_builtin_object_size (tree exp) +tree +fold_builtin_call_array (location_t loc, tree, + tree fn, + int n, + tree *argarray) { - tree ost; - int object_size_type; - tree fndecl = get_callee_fndecl (exp); + if (TREE_CODE (fn) != ADDR_EXPR) + return NULL_TREE; - if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + tree fndecl = TREE_OPERAND (fn, 0); + if (TREE_CODE (fndecl) == FUNCTION_DECL + && fndecl_built_in_p (fndecl)) { - error ("first argument of %qD must be a pointer, second integer constant", - fndecl); - expand_builtin_trap (); - return const0_rtx; + /* If last argument is __builtin_va_arg_pack (), arguments to this + function are not finalized yet. Defer folding until they are. */ + if (n && TREE_CODE (argarray[n - 1]) == CALL_EXPR) + { + tree fndecl2 = get_callee_fndecl (argarray[n - 1]); + if (fndecl2 && fndecl_built_in_p (fndecl2, BUILT_IN_VA_ARG_PACK)) + return NULL_TREE; + } + if (avoid_folding_inline_builtin (fndecl)) + return NULL_TREE; + if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) + return targetm.fold_builtin (fndecl, n, argarray, false); + else + return fold_builtin_n (loc, NULL_TREE, fndecl, argarray, n, false); } - ost = CALL_EXPR_ARG (exp, 1); - STRIP_NOPS (ost); + return NULL_TREE; +} - if (TREE_CODE (ost) != INTEGER_CST - || tree_int_cst_sgn (ost) < 0 - || compare_tree_int (ost, 3) > 0) - { - error ("last argument of %qD is not integer constant between 0 and 3", - fndecl); - expand_builtin_trap (); - return const0_rtx; - } +/* Construct a new CALL_EXPR using the tail of the argument list of EXP + along with N new arguments specified as the "..." parameters. SKIP + is the number of arguments in EXP to be omitted. This function is used + to do varargs-to-varargs transformations. */ - object_size_type = tree_to_shwi (ost); +static tree +rewrite_call_expr (location_t loc, tree exp, int skip, tree fndecl, int n, ...) +{ + va_list ap; + tree t; - return object_size_type < 2 ? constm1_rtx : const0_rtx; + va_start (ap, n); + t = rewrite_call_expr_valist (loc, call_expr_nargs (exp), + CALL_EXPR_ARGP (exp), skip, fndecl, n, ap); + va_end (ap); + + return t; } -/* Expand EXP, a call to the __mem{cpy,pcpy,move,set}_chk builtin. - FCODE is the BUILT_IN_* to use. - Return NULL_RTX if we failed; the caller should emit a normal call, - otherwise try to get the result in TARGET, if convenient (and in - mode MODE if that's convenient). */ +/* Validate a single argument ARG against a tree code CODE representing + a type. Return true when argument is valid. */ -static rtx -expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode, - enum built_in_function fcode) +static bool +validate_arg (const_tree arg, enum tree_code code) { - if (!validate_arglist (exp, - POINTER_TYPE, - fcode == BUILT_IN_MEMSET_CHK - ? INTEGER_TYPE : POINTER_TYPE, - INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) - return NULL_RTX; + if (!arg) + return false; + else if (code == POINTER_TYPE) + return POINTER_TYPE_P (TREE_TYPE (arg)); + else if (code == INTEGER_TYPE) + return INTEGRAL_TYPE_P (TREE_TYPE (arg)); + return code == TREE_CODE (TREE_TYPE (arg)); +} - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - tree len = CALL_EXPR_ARG (exp, 2); - tree size = CALL_EXPR_ARG (exp, 3); +/* 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 ellipses, otherwise the last specifier must be a + VOID_TYPE. - /* FIXME: Set access mode to write only for memset et al. */ - bool sizes_ok = check_access (exp, len, /*maxread=*/NULL_TREE, - /*srcstr=*/NULL_TREE, size, access_read_write); + This is the GIMPLE version of validate_arglist. Eventually we want to + completely convert builtins.c to work from GIMPLEs and the tree based + validate_arglist will then be removed. */ - if (!tree_fits_uhwi_p (size)) - return NULL_RTX; +bool +validate_gimple_arglist (const gcall *call, ...) +{ + enum tree_code code; + bool res = 0; + va_list ap; + const_tree arg; + size_t i; - if (tree_fits_uhwi_p (len) || integer_all_onesp (size)) - { - /* Avoid transforming the checking call to an ordinary one when - an overflow has been detected or when the call couldn't be - validated because the size is not constant. */ - if (!sizes_ok && !integer_all_onesp (size) && tree_int_cst_lt (size, len)) - return NULL_RTX; + va_start (ap, call); + i = 0; - tree fn = NULL_TREE; - /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume - mem{cpy,pcpy,move,set} is available. */ - switch (fcode) + do + { + code = (enum tree_code) va_arg (ap, int); + switch (code) { - case BUILT_IN_MEMCPY_CHK: - fn = builtin_decl_explicit (BUILT_IN_MEMCPY); - break; - case BUILT_IN_MEMPCPY_CHK: - fn = builtin_decl_explicit (BUILT_IN_MEMPCPY); - break; - case BUILT_IN_MEMMOVE_CHK: - fn = builtin_decl_explicit (BUILT_IN_MEMMOVE); - break; - case BUILT_IN_MEMSET_CHK: - fn = builtin_decl_explicit (BUILT_IN_MEMSET); - break; + 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 = (i == gimple_call_num_args (call)); + goto end; 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 = gimple_call_arg (call, i++); + if (!validate_arg (arg, code)) + goto end; break; } + } + while (1); - if (! fn) - return NULL_RTX; + /* We need gotos here since we can only have one VA_CLOSE in a + function. */ + end: ; + va_end (ap); - fn = build_call_nofold_loc (EXPR_LOCATION (exp), fn, 3, dest, src, len); - gcc_assert (TREE_CODE (fn) == CALL_EXPR); - CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); - return expand_expr (fn, target, mode, EXPAND_NORMAL); - } - else if (fcode == BUILT_IN_MEMSET_CHK) - return NULL_RTX; - else - { - unsigned int dest_align = get_pointer_alignment (dest); + return res; +} - /* If DEST is not a pointer type, call the normal function. */ - if (dest_align == 0) - return NULL_RTX; +/* Default target-specific builtin expander that does nothing. */ - /* If SRC and DEST are the same (and not volatile), do nothing. */ - if (operand_equal_p (src, dest, 0)) - { - tree expr; +rtx +default_expand_builtin (tree exp ATTRIBUTE_UNUSED, + rtx target ATTRIBUTE_UNUSED, + rtx subtarget ATTRIBUTE_UNUSED, + machine_mode mode ATTRIBUTE_UNUSED, + int ignore ATTRIBUTE_UNUSED) +{ + return NULL_RTX; +} - if (fcode != BUILT_IN_MEMPCPY_CHK) - { - /* Evaluate and ignore LEN in case it has side-effects. */ - expand_expr (len, const0_rtx, VOIDmode, EXPAND_NORMAL); - return expand_expr (dest, target, mode, EXPAND_NORMAL); - } +/* Returns true is EXP represents data that would potentially reside + in a readonly section. */ - expr = fold_build_pointer_plus (dest, len); - return expand_expr (expr, target, mode, EXPAND_NORMAL); - } +bool +readonly_data_expr (tree exp) +{ + STRIP_NOPS (exp); - /* __memmove_chk special case. */ - if (fcode == BUILT_IN_MEMMOVE_CHK) - { - unsigned int src_align = get_pointer_alignment (src); + if (TREE_CODE (exp) != ADDR_EXPR) + return false; - if (src_align == 0) - return NULL_RTX; + exp = get_base_address (TREE_OPERAND (exp, 0)); + if (!exp) + return false; - /* If src is categorized for a readonly section we can use - normal __memcpy_chk. */ - if (readonly_data_expr (src)) - { - tree fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK); - if (!fn) - return NULL_RTX; - fn = build_call_nofold_loc (EXPR_LOCATION (exp), fn, 4, - dest, src, len, size); - gcc_assert (TREE_CODE (fn) == CALL_EXPR); - CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); - return expand_expr (fn, target, mode, EXPAND_NORMAL); - } - } - return NULL_RTX; - } + /* Make sure we call decl_readonly_section only for trees it + can handle (since it returns true for everything it doesn't + understand). */ + if (TREE_CODE (exp) == STRING_CST + || TREE_CODE (exp) == CONSTRUCTOR + || (VAR_P (exp) && TREE_STATIC (exp))) + return decl_readonly_section (exp, 0); + else + return false; } -/* Emit warning if a buffer overflow is detected at compile time. */ +/* Simplify a call to the strpbrk builtin. S1 and S2 are the arguments + to the call, and TYPE is its return type. -static void -maybe_emit_chk_warning (tree exp, enum built_in_function fcode) -{ - /* The source string. */ - tree srcstr = NULL_TREE; - /* The size of the destination object returned by __builtin_object_size. */ - tree objsize = NULL_TREE; - /* The string that is being concatenated with (as in __strcat_chk) - or null if it isn't. */ - tree catstr = NULL_TREE; - /* The maximum length of the source sequence in a bounded operation - (such as __strncat_chk) or null if the operation isn't bounded - (such as __strcat_chk). */ - tree maxread = NULL_TREE; - /* The exact size of the access (such as in __strncpy_chk). */ - tree size = NULL_TREE; - /* The access by the function that's checked. Except for snprintf - both writing and reading is checked. */ - access_mode mode = access_read_write; + Return NULL_TREE if no simplification was possible, otherwise return the + simplified form of the call as a tree. - switch (fcode) - { - case BUILT_IN_STRCPY_CHK: - case BUILT_IN_STPCPY_CHK: - srcstr = CALL_EXPR_ARG (exp, 1); - objsize = CALL_EXPR_ARG (exp, 2); - break; + The simplified form may be a constant or other expression which + computes the same value, but in a more efficient manner (including + calls to other builtin functions). - case BUILT_IN_STRCAT_CHK: - /* For __strcat_chk the warning will be emitted only if overflowing - by at least strlen (dest) + 1 bytes. */ - catstr = CALL_EXPR_ARG (exp, 0); - srcstr = CALL_EXPR_ARG (exp, 1); - objsize = CALL_EXPR_ARG (exp, 2); - break; + The call may contain arguments which need to be evaluated, but + which are not useful to determine the result of the call. In + this case we return a chain of COMPOUND_EXPRs. The LHS of each + COMPOUND_EXPR will be an argument which must be evaluated. + COMPOUND_EXPRs are chained through their RHS. The RHS of the last + COMPOUND_EXPR in the chain will contain the tree for the simplified + form of the builtin function call. */ - case BUILT_IN_STRNCAT_CHK: - catstr = CALL_EXPR_ARG (exp, 0); - srcstr = CALL_EXPR_ARG (exp, 1); - maxread = CALL_EXPR_ARG (exp, 2); - objsize = CALL_EXPR_ARG (exp, 3); - break; +static tree +fold_builtin_strpbrk (location_t loc, tree, tree s1, tree s2, tree type) +{ + if (!validate_arg (s1, POINTER_TYPE) + || !validate_arg (s2, POINTER_TYPE)) + return NULL_TREE; - case BUILT_IN_STRNCPY_CHK: - case BUILT_IN_STPNCPY_CHK: - srcstr = CALL_EXPR_ARG (exp, 1); - size = CALL_EXPR_ARG (exp, 2); - objsize = CALL_EXPR_ARG (exp, 3); - break; + tree fn; + const char *p1, *p2; - case BUILT_IN_SNPRINTF_CHK: - case BUILT_IN_VSNPRINTF_CHK: - maxread = CALL_EXPR_ARG (exp, 1); - objsize = CALL_EXPR_ARG (exp, 3); - /* The only checked access the write to the destination. */ - mode = access_write_only; - break; - default: - gcc_unreachable (); - } + p2 = c_getstr (s2); + if (p2 == NULL) + return NULL_TREE; - if (catstr && maxread) + p1 = c_getstr (s1); + if (p1 != NULL) { - /* Check __strncat_chk. There is no way to determine the length - of the string to which the source string is being appended so - just warn when the length of the source string is not known. */ - check_strncat_sizes (exp, objsize); - return; + const char *r = strpbrk (p1, p2); + tree tem; + + if (r == NULL) + return build_int_cst (TREE_TYPE (s1), 0); + + /* Return an offset into the constant string argument. */ + tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1); + return fold_convert_loc (loc, type, tem); } - check_access (exp, size, maxread, srcstr, objsize, mode); -} + if (p2[0] == '\0') + /* strpbrk(x, "") == NULL. + Evaluate and ignore s1 in case it had side-effects. */ + return omit_one_operand_loc (loc, type, integer_zero_node, s1); -/* Emit warning if a buffer overflow is detected at compile time - in __sprintf_chk/__vsprintf_chk calls. */ + if (p2[1] != '\0') + return NULL_TREE; /* Really call strpbrk. */ -static void -maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode) -{ - tree size, len, fmt; - const char *fmt_str; - int nargs = call_expr_nargs (exp); + fn = builtin_decl_implicit (BUILT_IN_STRCHR); + if (!fn) + return NULL_TREE; - /* Verify the required arguments in the original call. */ + /* New argument list transforming strpbrk(s1, s2) to + strchr(s1, s2[0]). */ + return build_call_expr_loc (loc, fn, 2, s1, + build_int_cst (integer_type_node, p2[0])); +} - if (nargs < 4) - return; - size = CALL_EXPR_ARG (exp, 2); - fmt = CALL_EXPR_ARG (exp, 3); +/* Simplify a call to the strspn builtin. S1 and S2 are the arguments + to the call. - if (! tree_fits_uhwi_p (size) || integer_all_onesp (size)) - return; + Return NULL_TREE if no simplification was possible, otherwise return the + simplified form of the call as a tree. - /* Check whether the format is a literal string constant. */ - fmt_str = c_getstr (fmt); - if (fmt_str == NULL) - return; + The simplified form may be a constant or other expression which + computes the same value, but in a more efficient manner (including + calls to other builtin functions). - if (!init_target_chars ()) - return; + The call may contain arguments which need to be evaluated, but + which are not useful to determine the result of the call. In + this case we return a chain of COMPOUND_EXPRs. The LHS of each + COMPOUND_EXPR will be an argument which must be evaluated. + COMPOUND_EXPRs are chained through their RHS. The RHS of the last + COMPOUND_EXPR in the chain will contain the tree for the simplified + form of the builtin function call. */ - /* If the format doesn't contain % args or %%, we know its size. */ - if (strchr (fmt_str, target_percent) == 0) - len = build_int_cstu (size_type_node, strlen (fmt_str)); - /* If the format is "%s" and first ... argument is a string literal, - we know it too. */ - else if (fcode == BUILT_IN_SPRINTF_CHK - && strcmp (fmt_str, target_percent_s) == 0) - { - tree arg; +static tree +fold_builtin_strspn (location_t loc, tree expr, tree s1, tree s2) +{ + if (!validate_arg (s1, POINTER_TYPE) + || !validate_arg (s2, POINTER_TYPE)) + return NULL_TREE; - if (nargs < 5) - return; - arg = CALL_EXPR_ARG (exp, 4); - if (! POINTER_TYPE_P (TREE_TYPE (arg))) - return; + if (!check_nul_terminated_array (expr, s1) + || !check_nul_terminated_array (expr, s2)) + return NULL_TREE; - len = c_strlen (arg, 1); - if (!len || ! tree_fits_uhwi_p (len)) - return; - } - else - return; + const char *p1 = c_getstr (s1), *p2 = c_getstr (s2); + + /* If either argument is "", return NULL_TREE. */ + if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0')) + /* Evaluate and ignore both arguments in case either one has + side-effects. */ + return omit_two_operands_loc (loc, size_type_node, size_zero_node, + s1, s2); + return NULL_TREE; +} - /* Add one for the terminating nul. */ - len = fold_build2 (PLUS_EXPR, TREE_TYPE (len), len, size_one_node); +/* Simplify a call to the strcspn builtin. S1 and S2 are the arguments + to the call. - check_access (exp, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE, len, size, - access_write_only); -} + Return NULL_TREE if no simplification was possible, otherwise return the + simplified form of the call as a tree. -/* Return true if STMT is a call to an allocation function. Unless - ALL_ALLOC is set, consider only functions that return dynmamically - allocated objects. Otherwise return true even for all forms of - alloca (including VLA). */ + The simplified form may be a constant or other expression which + computes the same value, but in a more efficient manner (including + calls to other builtin functions). -static bool -fndecl_alloc_p (tree fndecl, bool all_alloc) + The call may contain arguments which need to be evaluated, but + which are not useful to determine the result of the call. In + this case we return a chain of COMPOUND_EXPRs. The LHS of each + COMPOUND_EXPR will be an argument which must be evaluated. + COMPOUND_EXPRs are chained through their RHS. The RHS of the last + COMPOUND_EXPR in the chain will contain the tree for the simplified + form of the builtin function call. */ + +static tree +fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2) { - if (!fndecl) - return false; + if (!validate_arg (s1, POINTER_TYPE) + || !validate_arg (s2, POINTER_TYPE)) + return NULL_TREE; - /* A call to operator new isn't recognized as one to a built-in. */ - if (DECL_IS_OPERATOR_NEW_P (fndecl)) - return true; + if (!check_nul_terminated_array (expr, s1) + || !check_nul_terminated_array (expr, s2)) + return NULL_TREE; - if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + /* If the first argument is "", return NULL_TREE. */ + const char *p1 = c_getstr (s1); + if (p1 && *p1 == '\0') { - switch (DECL_FUNCTION_CODE (fndecl)) - { - case BUILT_IN_ALLOCA: - case BUILT_IN_ALLOCA_WITH_ALIGN: - return all_alloc; - case BUILT_IN_ALIGNED_ALLOC: - case BUILT_IN_CALLOC: - case BUILT_IN_GOMP_ALLOC: - case BUILT_IN_MALLOC: - case BUILT_IN_REALLOC: - case BUILT_IN_STRDUP: - case BUILT_IN_STRNDUP: - return true; - default: - break; - } + /* Evaluate and ignore argument s2 in case it has + side-effects. */ + return omit_one_operand_loc (loc, size_type_node, + size_zero_node, s2); } - /* A function is considered an allocation function if it's declared - with attribute malloc with an argument naming its associated - deallocation function. */ - tree attrs = DECL_ATTRIBUTES (fndecl); - if (!attrs) - return false; - - for (tree allocs = attrs; - (allocs = lookup_attribute ("malloc", allocs)); - allocs = TREE_CHAIN (allocs)) + /* If the second argument is "", return __builtin_strlen(s1). */ + const char *p2 = c_getstr (s2); + if (p2 && *p2 == '\0') { - tree args = TREE_VALUE (allocs); - if (!args) - continue; - - if (TREE_VALUE (args)) - return true; - } - - return false; -} + tree fn = builtin_decl_implicit (BUILT_IN_STRLEN); -/* Return true if STMT is a call to an allocation function. A wrapper - around fndecl_alloc_p. */ + /* If the replacement _DECL isn't initialized, don't do the + transformation. */ + if (!fn) + return NULL_TREE; -static bool -gimple_call_alloc_p (gimple *stmt, bool all_alloc = false) -{ - return fndecl_alloc_p (gimple_call_fndecl (stmt), all_alloc); + return build_call_expr_loc (loc, fn, 1, s1); + } + return NULL_TREE; } -/* Return the zero-based number corresponding to the argument being - deallocated if STMT is a call to a deallocation function or UINT_MAX - if it isn't. */ +/* Fold the next_arg or va_start call EXP. Returns true if there was an error + produced. False otherwise. This is done so that we don't output the error + or warning twice or three times. */ -static unsigned -call_dealloc_argno (tree exp) +bool +fold_builtin_next_arg (tree exp, bool va_start_p) { - tree fndecl = get_callee_fndecl (exp); - if (!fndecl) - return UINT_MAX; - - return fndecl_dealloc_argno (fndecl); -} - -/* Return the zero-based number corresponding to the argument being - deallocated if FNDECL is a deallocation function or UINT_MAX - if it isn't. */ + tree fntype = TREE_TYPE (current_function_decl); + int nargs = call_expr_nargs (exp); + tree arg; + /* There is good chance the current input_location points inside the + definition of the va_start macro (perhaps on the token for + builtin) in a system header, so warnings will not be emitted. + Use the location in real source code. */ + location_t current_location = + linemap_unwind_to_first_non_reserved_loc (line_table, input_location, + NULL); -unsigned -fndecl_dealloc_argno (tree fndecl) -{ - /* A call to operator delete isn't recognized as one to a built-in. */ - if (DECL_IS_OPERATOR_DELETE_P (fndecl)) + if (!stdarg_p (fntype)) { - if (DECL_IS_REPLACEABLE_OPERATOR (fndecl)) - return 0; - - /* Avoid placement delete that's not been inlined. */ - tree fname = DECL_ASSEMBLER_NAME (fndecl); - if (id_equal (fname, "_ZdlPvS_") // ordinary form - || id_equal (fname, "_ZdaPvS_")) // array form - return UINT_MAX; - return 0; + error ("% used in function with fixed arguments"); + return true; } - /* TODO: Handle user-defined functions with attribute malloc? Handle - known non-built-ins like fopen? */ - if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + if (va_start_p) { - switch (DECL_FUNCTION_CODE (fndecl)) + if (va_start_p && (nargs != 2)) { - case BUILT_IN_FREE: - case BUILT_IN_REALLOC: - return 0; - default: - break; + error ("wrong number of arguments to function %"); + return true; } - return UINT_MAX; + arg = CALL_EXPR_ARG (exp, 1); } - - tree attrs = DECL_ATTRIBUTES (fndecl); - if (!attrs) - return UINT_MAX; - - for (tree atfree = attrs; - (atfree = lookup_attribute ("*dealloc", atfree)); - atfree = TREE_CHAIN (atfree)) + /* We use __builtin_va_start (ap, 0, 0) or __builtin_next_arg (0, 0) + when we checked the arguments and if needed issued a warning. */ + else { - tree alloc = TREE_VALUE (atfree); - if (!alloc) - continue; - - tree pos = TREE_CHAIN (alloc); - if (!pos) - return 0; - - pos = TREE_VALUE (pos); - return TREE_INT_CST_LOW (pos) - 1; + if (nargs == 0) + { + /* Evidently an out of date version of ; can't validate + va_start's second argument, but can still work as intended. */ + warning_at (current_location, + OPT_Wvarargs, + "%<__builtin_next_arg%> called without an argument"); + return true; + } + else if (nargs > 1) + { + error ("wrong number of arguments to function %<__builtin_next_arg%>"); + return true; + } + arg = CALL_EXPR_ARG (exp, 0); } - return UINT_MAX; -} - -/* Return true if DELC doesn't refer to an operator delete that's - suitable to call with a pointer returned from the operator new - described by NEWC. */ - -static bool -new_delete_mismatch_p (const demangle_component &newc, - const demangle_component &delc) -{ - if (newc.type != delc.type) - return true; + if (TREE_CODE (arg) == SSA_NAME + && SSA_NAME_VAR (arg)) + arg = SSA_NAME_VAR (arg); - switch (newc.type) + /* We destructively modify the call to be __builtin_va_start (ap, 0) + or __builtin_next_arg (0) the first time we see it, after checking + the arguments and if needed issuing a warning. */ + if (!integer_zerop (arg)) { - case DEMANGLE_COMPONENT_NAME: - { - int len = newc.u.s_name.len; - const char *news = newc.u.s_name.s; - const char *dels = delc.u.s_name.s; - if (len != delc.u.s_name.len || memcmp (news, dels, len)) - return true; - - if (news[len] == 'n') - { - if (news[len + 1] == 'a') - return dels[len] != 'd' || dels[len + 1] != 'a'; - if (news[len + 1] == 'w') - return dels[len] != 'd' || dels[len + 1] != 'l'; - } - return false; - } - - case DEMANGLE_COMPONENT_OPERATOR: - /* Operator mismatches are handled above. */ - return false; - - case DEMANGLE_COMPONENT_EXTENDED_OPERATOR: - if (newc.u.s_extended_operator.args != delc.u.s_extended_operator.args) - return true; - return new_delete_mismatch_p (*newc.u.s_extended_operator.name, - *delc.u.s_extended_operator.name); - - case DEMANGLE_COMPONENT_FIXED_TYPE: - if (newc.u.s_fixed.accum != delc.u.s_fixed.accum - || newc.u.s_fixed.sat != delc.u.s_fixed.sat) - return true; - return new_delete_mismatch_p (*newc.u.s_fixed.length, - *delc.u.s_fixed.length); + tree last_parm = tree_last (DECL_ARGUMENTS (current_function_decl)); - case DEMANGLE_COMPONENT_CTOR: - if (newc.u.s_ctor.kind != delc.u.s_ctor.kind) - return true; - return new_delete_mismatch_p (*newc.u.s_ctor.name, - *delc.u.s_ctor.name); + /* Strip off all nops for the sake of the comparison. This + is not quite the same as STRIP_NOPS. It does more. + We must also strip off INDIRECT_EXPR for C++ reference + parameters. */ + while (CONVERT_EXPR_P (arg) + || TREE_CODE (arg) == INDIRECT_REF) + arg = TREE_OPERAND (arg, 0); + if (arg != last_parm) + { + /* FIXME: Sometimes with the tree optimizers we can get the + not the last argument even though the user used the last + argument. We just warn and set the arg to be the last + argument so that we will get wrong-code because of + it. */ + warning_at (current_location, + OPT_Wvarargs, + "second parameter of % not last named argument"); + } - case DEMANGLE_COMPONENT_DTOR: - if (newc.u.s_dtor.kind != delc.u.s_dtor.kind) - return true; - return new_delete_mismatch_p (*newc.u.s_dtor.name, - *delc.u.s_dtor.name); + /* Undefined by C99 7.15.1.4p4 (va_start): + "If the parameter parmN is declared with the register storage + class, with a function or array type, or with a type that is + not compatible with the type that results after application of + the default argument promotions, the behavior is undefined." + */ + else if (DECL_REGISTER (arg)) + { + warning_at (current_location, + OPT_Wvarargs, + "undefined behavior when second parameter of " + "% is declared with % storage"); + } - case DEMANGLE_COMPONENT_BUILTIN_TYPE: - { - /* The demangler API provides no better way to compare built-in - types except to by comparing their demangled names. */ - size_t nsz, dsz; - demangle_component *pnc = const_cast(&newc); - demangle_component *pdc = const_cast(&delc); - char *nts = cplus_demangle_print (0, pnc, 16, &nsz); - char *dts = cplus_demangle_print (0, pdc, 16, &dsz); - if (!nts != !dts) - return true; - bool mismatch = strcmp (nts, dts); - free (nts); - free (dts); - return mismatch; - } + /* We want to verify the second parameter just once before the tree + optimizers are run and then avoid keeping it in the tree, + as otherwise we could warn even for correct code like: + void foo (int i, ...) + { va_list ap; i++; va_start (ap, i); va_end (ap); } */ + if (va_start_p) + CALL_EXPR_ARG (exp, 1) = integer_zero_node; + else + CALL_EXPR_ARG (exp, 0) = integer_zero_node; + } + return false; +} - case DEMANGLE_COMPONENT_SUB_STD: - if (newc.u.s_string.len != delc.u.s_string.len) - return true; - return memcmp (newc.u.s_string.string, delc.u.s_string.string, - newc.u.s_string.len); - case DEMANGLE_COMPONENT_FUNCTION_PARAM: - case DEMANGLE_COMPONENT_TEMPLATE_PARAM: - return newc.u.s_number.number != delc.u.s_number.number; +/* Expand a call EXP to __builtin_object_size. */ - case DEMANGLE_COMPONENT_CHARACTER: - return newc.u.s_character.character != delc.u.s_character.character; +static rtx +expand_builtin_object_size (tree exp) +{ + tree ost; + int object_size_type; + tree fndecl = get_callee_fndecl (exp); - case DEMANGLE_COMPONENT_DEFAULT_ARG: - case DEMANGLE_COMPONENT_LAMBDA: - if (newc.u.s_unary_num.num != delc.u.s_unary_num.num) - return true; - return new_delete_mismatch_p (*newc.u.s_unary_num.sub, - *delc.u.s_unary_num.sub); - default: - break; + if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + { + error ("first argument of %qD must be a pointer, second integer constant", + fndecl); + expand_builtin_trap (); + return const0_rtx; } - if (!newc.u.s_binary.left != !delc.u.s_binary.left) - return true; + ost = CALL_EXPR_ARG (exp, 1); + STRIP_NOPS (ost); - if (!newc.u.s_binary.left) - return false; + if (TREE_CODE (ost) != INTEGER_CST + || tree_int_cst_sgn (ost) < 0 + || compare_tree_int (ost, 3) > 0) + { + error ("last argument of %qD is not integer constant between 0 and 3", + fndecl); + expand_builtin_trap (); + return const0_rtx; + } - if (new_delete_mismatch_p (*newc.u.s_binary.left, *delc.u.s_binary.left) - || !newc.u.s_binary.right != !delc.u.s_binary.right) - return true; + object_size_type = tree_to_shwi (ost); - if (newc.u.s_binary.right) - return new_delete_mismatch_p (*newc.u.s_binary.right, - *delc.u.s_binary.right); - return false; + return object_size_type < 2 ? constm1_rtx : const0_rtx; } -/* Return true if DELETE_DECL is an operator delete that's not suitable - to call with a pointer returned fron NEW_DECL. */ +/* Expand EXP, a call to the __mem{cpy,pcpy,move,set}_chk builtin. + FCODE is the BUILT_IN_* to use. + Return NULL_RTX if we failed; the caller should emit a normal call, + otherwise try to get the result in TARGET, if convenient (and in + mode MODE if that's convenient). */ -static bool -new_delete_mismatch_p (tree new_decl, tree delete_decl) +static rtx +expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode, + enum built_in_function fcode) { - tree new_name = DECL_ASSEMBLER_NAME (new_decl); - tree delete_name = DECL_ASSEMBLER_NAME (delete_decl); - - /* valid_new_delete_pair_p() returns a conservative result (currently - it only handles global operators). A true result is reliable but - a false result doesn't necessarily mean the operators don't match. */ - if (valid_new_delete_pair_p (new_name, delete_name)) - return false; - - /* For anything not handled by valid_new_delete_pair_p() such as member - operators compare the individual demangled components of the mangled - name. */ - const char *new_str = IDENTIFIER_POINTER (new_name); - const char *del_str = IDENTIFIER_POINTER (delete_name); + if (!validate_arglist (exp, + POINTER_TYPE, + fcode == BUILT_IN_MEMSET_CHK + ? INTEGER_TYPE : POINTER_TYPE, + INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; - void *np = NULL, *dp = NULL; - demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np); - demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp); - bool mismatch = new_delete_mismatch_p (*ndc, *ddc); - free (np); - free (dp); - return mismatch; -} + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + tree len = CALL_EXPR_ARG (exp, 2); + tree size = CALL_EXPR_ARG (exp, 3); -/* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation - functions. Return true if the latter is suitable to deallocate objects - allocated by calls to the former. */ + /* FIXME: Set access mode to write only for memset et al. */ + bool sizes_ok = check_access (exp, len, /*maxread=*/NULL_TREE, + /*srcstr=*/NULL_TREE, size, access_read_write); -static bool -matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) -{ - /* Set to alloc_kind_t::builtin if ALLOC_DECL is associated with - a built-in deallocator. */ - enum class alloc_kind_t { none, builtin, user } - alloc_dealloc_kind = alloc_kind_t::none; + if (!tree_fits_uhwi_p (size)) + return NULL_RTX; - if (DECL_IS_OPERATOR_NEW_P (alloc_decl)) + if (tree_fits_uhwi_p (len) || integer_all_onesp (size)) { - if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) - /* Return true iff both functions are of the same array or - singleton form and false otherwise. */ - return !new_delete_mismatch_p (alloc_decl, dealloc_decl); + /* Avoid transforming the checking call to an ordinary one when + an overflow has been detected or when the call couldn't be + validated because the size is not constant. */ + if (!sizes_ok && !integer_all_onesp (size) && tree_int_cst_lt (size, len)) + return NULL_RTX; - /* Return false for deallocation functions that are known not - to match. */ - if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) - || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) - return false; - /* Otherwise proceed below to check the deallocation function's - "*dealloc" attributes to look for one that mentions this operator - new. */ - } - else if (fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL)) - { - switch (DECL_FUNCTION_CODE (alloc_decl)) + tree fn = NULL_TREE; + /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume + mem{cpy,pcpy,move,set} is available. */ + switch (fcode) { - case BUILT_IN_ALLOCA: - case BUILT_IN_ALLOCA_WITH_ALIGN: - return false; - - case BUILT_IN_ALIGNED_ALLOC: - case BUILT_IN_CALLOC: - case BUILT_IN_GOMP_ALLOC: - case BUILT_IN_MALLOC: - case BUILT_IN_REALLOC: - case BUILT_IN_STRDUP: - case BUILT_IN_STRNDUP: - if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) - return false; - - if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) - || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) - return true; - - alloc_dealloc_kind = alloc_kind_t::builtin; + case BUILT_IN_MEMCPY_CHK: + fn = builtin_decl_explicit (BUILT_IN_MEMCPY); + break; + case BUILT_IN_MEMPCPY_CHK: + fn = builtin_decl_explicit (BUILT_IN_MEMPCPY); + break; + case BUILT_IN_MEMMOVE_CHK: + fn = builtin_decl_explicit (BUILT_IN_MEMMOVE); + break; + case BUILT_IN_MEMSET_CHK: + fn = builtin_decl_explicit (BUILT_IN_MEMSET); break; - default: break; } - } - - /* Set if DEALLOC_DECL both allocates and deallocates. */ - alloc_kind_t realloc_kind = alloc_kind_t::none; - - if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL)) - { - built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl); - if (dealloc_code == BUILT_IN_REALLOC) - realloc_kind = alloc_kind_t::builtin; - - for (tree amats = DECL_ATTRIBUTES (alloc_decl); - (amats = lookup_attribute ("malloc", amats)); - amats = TREE_CHAIN (amats)) - { - tree args = TREE_VALUE (amats); - if (!args) - continue; - tree fndecl = TREE_VALUE (args); - if (!fndecl || !DECL_P (fndecl)) - continue; + if (! fn) + return NULL_RTX; - if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL) - && dealloc_code == DECL_FUNCTION_CODE (fndecl)) - return true; - } + fn = build_call_nofold_loc (EXPR_LOCATION (exp), fn, 3, dest, src, len); + gcc_assert (TREE_CODE (fn) == CALL_EXPR); + CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); + return expand_expr (fn, target, mode, EXPAND_NORMAL); } - - const bool alloc_builtin = fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL); - alloc_kind_t realloc_dealloc_kind = alloc_kind_t::none; - - /* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list - of its associated allocation functions for ALLOC_DECL. - If the corresponding ALLOC_DECL is found they're a matching pair, - otherwise they're not. - With DDATS set to the Deallocator's *Dealloc ATtributes... */ - for (tree ddats = DECL_ATTRIBUTES (dealloc_decl); - (ddats = lookup_attribute ("*dealloc", ddats)); - ddats = TREE_CHAIN (ddats)) + else if (fcode == BUILT_IN_MEMSET_CHK) + return NULL_RTX; + else { - tree args = TREE_VALUE (ddats); - if (!args) - continue; - - tree alloc = TREE_VALUE (args); - if (!alloc) - continue; + unsigned int dest_align = get_pointer_alignment (dest); - if (alloc == DECL_NAME (dealloc_decl)) - realloc_kind = alloc_kind_t::user; + /* If DEST is not a pointer type, call the normal function. */ + if (dest_align == 0) + return NULL_RTX; - if (DECL_P (alloc)) + /* If SRC and DEST are the same (and not volatile), do nothing. */ + if (operand_equal_p (src, dest, 0)) { - gcc_checking_assert (fndecl_built_in_p (alloc, BUILT_IN_NORMAL)); + tree expr; - switch (DECL_FUNCTION_CODE (alloc)) + if (fcode != BUILT_IN_MEMPCPY_CHK) { - case BUILT_IN_ALIGNED_ALLOC: - case BUILT_IN_CALLOC: - case BUILT_IN_GOMP_ALLOC: - case BUILT_IN_MALLOC: - case BUILT_IN_REALLOC: - case BUILT_IN_STRDUP: - case BUILT_IN_STRNDUP: - realloc_dealloc_kind = alloc_kind_t::builtin; - break; - default: - break; + /* Evaluate and ignore LEN in case it has side-effects. */ + expand_expr (len, const0_rtx, VOIDmode, EXPAND_NORMAL); + return expand_expr (dest, target, mode, EXPAND_NORMAL); } - if (!alloc_builtin) - continue; - - if (DECL_FUNCTION_CODE (alloc) != DECL_FUNCTION_CODE (alloc_decl)) - continue; - - return true; + expr = fold_build_pointer_plus (dest, len); + return expand_expr (expr, target, mode, EXPAND_NORMAL); } - if (alloc == DECL_NAME (alloc_decl)) - return true; - } - - if (realloc_kind == alloc_kind_t::none) - return false; + /* __memmove_chk special case. */ + if (fcode == BUILT_IN_MEMMOVE_CHK) + { + unsigned int src_align = get_pointer_alignment (src); - hash_set common_deallocs; - /* Special handling for deallocators. Iterate over both the allocator's - and the reallocator's associated deallocator functions looking for - the first one in common. If one is found, the de/reallocator is - a match for the allocator even though the latter isn't directly - associated with the former. This simplifies declarations in system - headers. - With AMATS set to the Allocator's Malloc ATtributes, - and RMATS set to Reallocator's Malloc ATtributes... */ - for (tree amats = DECL_ATTRIBUTES (alloc_decl), - rmats = DECL_ATTRIBUTES (dealloc_decl); - (amats = lookup_attribute ("malloc", amats)) - || (rmats = lookup_attribute ("malloc", rmats)); - amats = amats ? TREE_CHAIN (amats) : NULL_TREE, - rmats = rmats ? TREE_CHAIN (rmats) : NULL_TREE) - { - if (tree args = amats ? TREE_VALUE (amats) : NULL_TREE) - if (tree adealloc = TREE_VALUE (args)) - { - if (DECL_P (adealloc) - && fndecl_built_in_p (adealloc, BUILT_IN_NORMAL)) - { - built_in_function fncode = DECL_FUNCTION_CODE (adealloc); - if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) - { - if (realloc_kind == alloc_kind_t::builtin) - return true; - alloc_dealloc_kind = alloc_kind_t::builtin; - } - continue; - } - - common_deallocs.add (adealloc); - } + if (src_align == 0) + return NULL_RTX; - if (tree args = rmats ? TREE_VALUE (rmats) : NULL_TREE) - if (tree ddealloc = TREE_VALUE (args)) - { - if (DECL_P (ddealloc) - && fndecl_built_in_p (ddealloc, BUILT_IN_NORMAL)) - { - built_in_function fncode = DECL_FUNCTION_CODE (ddealloc); - if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) - { - if (alloc_dealloc_kind == alloc_kind_t::builtin) - return true; - realloc_dealloc_kind = alloc_kind_t::builtin; - } - continue; - } - - if (common_deallocs.add (ddealloc)) - return true; - } + /* If src is categorized for a readonly section we can use + normal __memcpy_chk. */ + if (readonly_data_expr (src)) + { + tree fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK); + if (!fn) + return NULL_RTX; + fn = build_call_nofold_loc (EXPR_LOCATION (exp), fn, 4, + dest, src, len, size); + gcc_assert (TREE_CODE (fn) == CALL_EXPR); + CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp); + return expand_expr (fn, target, mode, EXPAND_NORMAL); + } + } + return NULL_RTX; } - - /* Succeed only if ALLOC_DECL and the reallocator DEALLOC_DECL share - a built-in deallocator. */ - return (alloc_dealloc_kind == alloc_kind_t::builtin - && realloc_dealloc_kind == alloc_kind_t::builtin); } -/* Return true if DEALLOC_DECL is a function suitable to deallocate - objectes allocated by the ALLOC call. */ +/* Emit warning if a buffer overflow is detected at compile time. */ -static bool -matching_alloc_calls_p (gimple *alloc, tree dealloc_decl) +static void +maybe_emit_chk_warning (tree exp, enum built_in_function fcode) { - tree alloc_decl = gimple_call_fndecl (alloc); - if (!alloc_decl) - return true; - - return matching_alloc_calls_p (alloc_decl, dealloc_decl); -} + /* The source string. */ + tree srcstr = NULL_TREE; + /* The size of the destination object returned by __builtin_object_size. */ + tree objsize = NULL_TREE; + /* The string that is being concatenated with (as in __strcat_chk) + or null if it isn't. */ + tree catstr = NULL_TREE; + /* The maximum length of the source sequence in a bounded operation + (such as __strncat_chk) or null if the operation isn't bounded + (such as __strcat_chk). */ + tree maxread = NULL_TREE; + /* The exact size of the access (such as in __strncpy_chk). */ + tree size = NULL_TREE; + /* The access by the function that's checked. Except for snprintf + both writing and reading is checked. */ + access_mode mode = access_read_write; -/* Diagnose a call EXP to deallocate a pointer referenced by AREF if it - includes a nonzero offset. Such a pointer cannot refer to the beginning - of an allocated object. A negative offset may refer to it only if - the target pointer is unknown. */ + switch (fcode) + { + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STPCPY_CHK: + srcstr = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 2); + break; -static bool -warn_dealloc_offset (location_t loc, tree exp, const access_ref &aref) -{ - if (aref.deref || aref.offrng[0] <= 0 || aref.offrng[1] <= 0) - return false; + case BUILT_IN_STRCAT_CHK: + /* For __strcat_chk the warning will be emitted only if overflowing + by at least strlen (dest) + 1 bytes. */ + catstr = CALL_EXPR_ARG (exp, 0); + srcstr = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 2); + break; - tree dealloc_decl = get_callee_fndecl (exp); - if (!dealloc_decl) - return false; + case BUILT_IN_STRNCAT_CHK: + catstr = CALL_EXPR_ARG (exp, 0); + srcstr = CALL_EXPR_ARG (exp, 1); + maxread = CALL_EXPR_ARG (exp, 2); + objsize = CALL_EXPR_ARG (exp, 3); + break; - if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl) - && !DECL_IS_REPLACEABLE_OPERATOR (dealloc_decl)) - { - /* A call to a user-defined operator delete with a pointer plus offset - may be valid if it's returned from an unknown function (i.e., one - that's not operator new). */ - if (TREE_CODE (aref.ref) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); - if (is_gimple_call (def_stmt)) - { - tree alloc_decl = gimple_call_fndecl (def_stmt); - if (!alloc_decl || !DECL_IS_OPERATOR_NEW_P (alloc_decl)) - return false; - } - } - } + case BUILT_IN_STRNCPY_CHK: + case BUILT_IN_STPNCPY_CHK: + srcstr = CALL_EXPR_ARG (exp, 1); + size = CALL_EXPR_ARG (exp, 2); + objsize = CALL_EXPR_ARG (exp, 3); + break; - char offstr[80]; - offstr[0] = '\0'; - if (wi::fits_shwi_p (aref.offrng[0])) - { - if (aref.offrng[0] == aref.offrng[1] - || !wi::fits_shwi_p (aref.offrng[1])) - sprintf (offstr, " %lli", - (long long)aref.offrng[0].to_shwi ()); - else - sprintf (offstr, " [%lli, %lli]", - (long long)aref.offrng[0].to_shwi (), - (long long)aref.offrng[1].to_shwi ()); + case BUILT_IN_SNPRINTF_CHK: + case BUILT_IN_VSNPRINTF_CHK: + maxread = CALL_EXPR_ARG (exp, 1); + objsize = CALL_EXPR_ARG (exp, 3); + /* The only checked access the write to the destination. */ + mode = access_write_only; + break; + default: + gcc_unreachable (); } - if (!warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on pointer %qE with nonzero offset%s", - dealloc_decl, aref.ref, offstr)) - return false; - - if (DECL_P (aref.ref)) - inform (DECL_SOURCE_LOCATION (aref.ref), "declared here"); - else if (TREE_CODE (aref.ref) == SSA_NAME) + if (catstr && maxread) { - gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); - if (is_gimple_call (def_stmt)) - { - location_t def_loc = gimple_location (def_stmt); - tree alloc_decl = gimple_call_fndecl (def_stmt); - if (alloc_decl) - inform (def_loc, - "returned from %qD", alloc_decl); - else if (tree alloc_fntype = gimple_call_fntype (def_stmt)) - inform (def_loc, - "returned from %qT", alloc_fntype); - else - inform (def_loc, "obtained here"); - } + /* Check __strncat_chk. There is no way to determine the length + of the string to which the source string is being appended so + just warn when the length of the source string is not known. */ + check_strncat_sizes (exp, objsize); + return; } - return true; + check_access (exp, size, maxread, srcstr, objsize, mode); } -/* Issue a warning if a deallocation function such as free, realloc, - or C++ operator delete is called with an argument not returned by - a matching allocation function such as malloc or the corresponding - form of C++ operatorn new. */ +/* Emit warning if a buffer overflow is detected at compile time + in __sprintf_chk/__vsprintf_chk calls. */ -void -maybe_emit_free_warning (tree exp) +static void +maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode) { - tree fndecl = get_callee_fndecl (exp); - if (!fndecl) - return; + tree size, len, fmt; + const char *fmt_str; + int nargs = call_expr_nargs (exp); - unsigned argno = call_dealloc_argno (exp); - if ((unsigned) call_expr_nargs (exp) <= argno) - return; + /* Verify the required arguments in the original call. */ - tree ptr = CALL_EXPR_ARG (exp, argno); - if (integer_zerop (ptr)) + if (nargs < 4) return; + size = CALL_EXPR_ARG (exp, 2); + fmt = CALL_EXPR_ARG (exp, 3); - access_ref aref; - if (!compute_objsize (ptr, 0, &aref)) + if (! tree_fits_uhwi_p (size) || integer_all_onesp (size)) return; - tree ref = aref.ref; - if (integer_zerop (ref)) + /* Check whether the format is a literal string constant. */ + fmt_str = c_getstr (fmt); + if (fmt_str == NULL) return; - tree dealloc_decl = get_callee_fndecl (exp); - location_t loc = EXPR_LOCATION (exp); + if (!init_target_chars ()) + return; - if (DECL_P (ref) || EXPR_P (ref)) + /* If the format doesn't contain % args or %%, we know its size. */ + if (strchr (fmt_str, target_percent) == 0) + len = build_int_cstu (size_type_node, strlen (fmt_str)); + /* If the format is "%s" and first ... argument is a string literal, + we know it too. */ + else if (fcode == BUILT_IN_SPRINTF_CHK + && strcmp (fmt_str, target_percent_s) == 0) { - /* Diagnose freeing a declared object. */ - if (aref.ref_declared () - && warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on unallocated object %qD", - dealloc_decl, ref)) - { - loc = (DECL_P (ref) - ? DECL_SOURCE_LOCATION (ref) - : EXPR_LOCATION (ref)); - inform (loc, "declared here"); - return; - } + tree arg; - /* Diagnose freeing a pointer that includes a positive offset. - Such a pointer cannot refer to the beginning of an allocated - object. A negative offset may refer to it. */ - if (aref.sizrng[0] != aref.sizrng[1] - && warn_dealloc_offset (loc, exp, aref)) + if (nargs < 5) + return; + arg = CALL_EXPR_ARG (exp, 4); + if (! POINTER_TYPE_P (TREE_TYPE (arg))) + return; + + len = c_strlen (arg, 1); + if (!len || ! tree_fits_uhwi_p (len)) return; } - else if (CONSTANT_CLASS_P (ref)) - { - if (warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on a pointer to an unallocated " - "object %qE", dealloc_decl, ref)) - { - if (TREE_CODE (ptr) == SSA_NAME) - { - gimple *def_stmt = SSA_NAME_DEF_STMT (ptr); - if (is_gimple_assign (def_stmt)) - { - location_t loc = gimple_location (def_stmt); - inform (loc, "assigned here"); - } - } - return; - } - } - else if (TREE_CODE (ref) == SSA_NAME) - { - /* Also warn if the pointer argument refers to the result - of an allocation call like alloca or VLA. */ - gimple *def_stmt = SSA_NAME_DEF_STMT (ref); - if (is_gimple_call (def_stmt)) - { - bool warned = false; - if (gimple_call_alloc_p (def_stmt)) - { - if (matching_alloc_calls_p (def_stmt, dealloc_decl)) - { - if (warn_dealloc_offset (loc, exp, aref)) - return; - } - else - { - tree alloc_decl = gimple_call_fndecl (def_stmt); - const opt_code opt = - (DECL_IS_OPERATOR_NEW_P (alloc_decl) - || DECL_IS_OPERATOR_DELETE_P (dealloc_decl) - ? OPT_Wmismatched_new_delete - : OPT_Wmismatched_dealloc); - warned = warning_at (loc, opt, - "%qD called on pointer returned " - "from a mismatched allocation " - "function", dealloc_decl); - } - } - else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA) - || gimple_call_builtin_p (def_stmt, - BUILT_IN_ALLOCA_WITH_ALIGN)) - warned = warning_at (loc, OPT_Wfree_nonheap_object, - "%qD called on pointer to " - "an unallocated object", - dealloc_decl); - else if (warn_dealloc_offset (loc, exp, aref)) - return; - - if (warned) - { - tree fndecl = gimple_call_fndecl (def_stmt); - inform (gimple_location (def_stmt), - "returned from %qD", fndecl); - return; - } - } - else if (gimple_nop_p (def_stmt)) - { - ref = SSA_NAME_VAR (ref); - /* Diagnose freeing a pointer that includes a positive offset. */ - if (TREE_CODE (ref) == PARM_DECL - && !aref.deref - && aref.sizrng[0] != aref.sizrng[1] - && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, exp, aref)) - return; - } - } + else + return; + + /* Add one for the terminating nul. */ + len = fold_build2 (PLUS_EXPR, TREE_TYPE (len), len, size_one_node); + + check_access (exp, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE, len, size, + access_write_only); } /* Fold a call to __builtin_object_size with arguments PTR and OST, diff --git a/gcc/builtins.h b/gcc/builtins.h index a64ece3f1cd9..b580635a09f0 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -149,223 +149,10 @@ extern bool target_char_cst_p (tree t, char *p); extern internal_fn associated_internal_fn (tree); extern internal_fn replacement_internal_fn (gcall *); -extern bool check_nul_terminated_array (tree, tree, tree = NULL_TREE); -extern void warn_string_no_nul (location_t, tree, const char *, tree, - tree, tree = NULL_TREE, bool = false, - const wide_int[2] = NULL); -extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); extern bool builtin_with_linkage_p (tree); -/* Describes recursion limits used by functions that follow use-def - chains of SSA_NAMEs. */ - -class ssa_name_limit_t -{ - bitmap visited; /* Bitmap of visited SSA_NAMEs. */ - unsigned ssa_def_max; /* Longest chain of SSA_NAMEs to follow. */ - - /* Not copyable or assignable. */ - DISABLE_COPY_AND_ASSIGN (ssa_name_limit_t); - -public: - - ssa_name_limit_t () - : visited (), - ssa_def_max (param_ssa_name_def_chain_limit) { } - - /* Set a bit for the PHI in VISITED and return true if it wasn't - already set. */ - bool visit_phi (tree); - /* Clear a bit for the PHI in VISITED. */ - void leave_phi (tree); - /* Return false if the SSA_NAME chain length counter has reached - the limit, otherwise increment the counter and return true. */ - bool next (); - - /* 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 next_phi (tree); - - ~ssa_name_limit_t (); -}; - -class pointer_query; - -/* Describes a reference to an object used in an access. */ -struct access_ref -{ - /* Set the bounds of the reference to at most as many bytes - as the first argument or unknown when null, and at least - one when the second argument is true unless the first one - is a constant zero. */ - access_ref (tree = NULL_TREE, bool = false); - - /* Return the PHI node REF refers to or null if it doesn't. */ - gphi *phi () const; - - /* Return the object to which REF refers. */ - tree get_ref (vec *, access_ref * = NULL, int = 1, - ssa_name_limit_t * = NULL, pointer_query * = NULL) const; - - /* Return true if OFFRNG is the constant zero. */ - bool offset_zero () const - { - return offrng[0] == 0 && offrng[1] == 0; - } - - /* Return true if OFFRNG is bounded to a subrange of offset values - valid for the largest possible object. */ - bool offset_bounded () const; - - /* Return the maximum amount of space remaining and if non-null, set - argument to the minimum. */ - offset_int size_remaining (offset_int * = NULL) const; - -/* Return true if the offset and object size are in range for SIZE. */ - bool offset_in_range (const offset_int &) const; - - /* Return true if *THIS is an access to a declared object. */ - bool ref_declared () const - { - return DECL_P (ref) && base0 && deref < 1; - } - - /* Set the size range to the maximum. */ - void set_max_size_range () - { - sizrng[0] = 0; - sizrng[1] = wi::to_offset (max_object_size ()); - } - - /* Add OFF to the offset range. */ - void add_offset (const offset_int &off) - { - add_offset (off, off); - } - - /* Add the range [MIN, MAX] to the offset range. */ - void add_offset (const offset_int &, const offset_int &); - - /* Add the maximum representable offset to the offset range. */ - void add_max_offset () - { - offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); - add_offset (-maxoff - 1, maxoff); - } - - /* Issue an informational message describing the target of an access - with the given mode. */ - void inform_access (access_mode) const; - - /* Reference to the accessed object(s). */ - tree ref; - - /* Range of byte offsets into and sizes of the object(s). */ - offset_int offrng[2]; - offset_int sizrng[2]; - /* The minimum and maximum offset computed. */ - offset_int offmax[2]; - /* Range of the bound of the access: denotes that the access - is at least BNDRNG[0] bytes but no more than BNDRNG[1]. - For string functions the size of the actual access is - further constrained by the length of the string. */ - offset_int bndrng[2]; - - /* Used to fold integer expressions when called from front ends. */ - tree (*eval)(tree); - /* Positive when REF is dereferenced, negative when its address is - taken. */ - int deref; - /* Set if trailing one-element arrays should be treated as flexible - array members. */ - bool trail1special; - /* Set if valid offsets must start at zero (for declared and allocated - objects but not for others referenced by pointers). */ - bool base0; - /* Set if REF refers to a function array parameter not declared - static. */ - bool parmarray; -}; - -class range_query; - -/* Queries and caches compute_objsize results. */ -class pointer_query -{ - DISABLE_COPY_AND_ASSIGN (pointer_query); - -public: - /* Type of the two-level cache object defined by clients of the class - to have pointer SSA_NAMEs cached for speedy access. */ - struct cache_type - { - /* 1-based indices into cache. */ - vec indices; - /* The cache itself. */ - vec access_refs; - }; - - /* Construct an object with the given Ranger instance and cache. */ - explicit pointer_query (range_query * = NULL, cache_type * = NULL); - - /* Retrieve the access_ref for a variable from cache if it's there. */ - const access_ref* get_ref (tree, int = 1) const; - - /* Retrieve the access_ref for a variable from cache or compute it. */ - bool get_ref (tree, access_ref*, int = 1); - - /* Add an access_ref for the SSA_NAME to the cache. */ - void put_ref (tree, const access_ref&, int = 1); - - /* Flush the cache. */ - void flush_cache (); - - /* A Ranger instance. May be null to use global ranges. */ - range_query *rvals; - /* Cache of SSA_NAMEs. May be null to disable caching. */ - cache_type *var_cache; - - /* Cache performance counters. */ - mutable unsigned hits; - mutable unsigned misses; - mutable unsigned failures; - mutable unsigned depth; - mutable unsigned max_depth; -}; - -/* Describes a pair of references used in an access by built-in - functions like memcpy. */ -struct access_data -{ - /* Set the access to at most MAXWRITE and MAXREAD bytes, and - at least 1 when MINWRITE or MINREAD, respectively, is set. */ - access_data (tree expr, access_mode mode, - tree maxwrite = NULL_TREE, bool minwrite = false, - tree maxread = NULL_TREE, bool minread = false) - : call (expr), - dst (maxwrite, minwrite), src (maxread, minread), mode (mode) { } - - /* Built-in function call. */ - tree call; - /* Destination and source of the access. */ - access_ref dst, src; - /* Read-only for functions like memcmp or strlen, write-only - for memset, read-write for memcpy or strcat. */ - access_mode mode; -}; - -extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, - range_query * = NULL); -extern tree gimple_parm_array_size (tree, wide_int[2], bool * = NULL); - -extern tree compute_objsize (tree, int, access_ref *, range_query * = NULL); -/* Legacy/transitional API. Should not be used in new code. */ -extern tree compute_objsize (tree, int, access_ref *, pointer_query *); -extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, - range_query * = NULL); +class access_data; extern bool check_access (tree, tree, tree, tree, tree, access_mode, const access_data * = NULL); -extern void maybe_emit_free_warning (tree); #endif /* GCC_BUILTINS_H */ diff --git a/gcc/calls.c b/gcc/calls.c index c54c57206c7c..795d21414c18 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -60,6 +60,7 @@ along with GCC; see the file COPYING3. If not see #include "gimple-fold.h" #include "attr-fnspec.h" #include "value-query.h" +#include "pointer-query.h" #include "tree-pretty-print.h" @@ -2628,10 +2629,6 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, /* Check attribute access arguments. */ maybe_warn_rdwr_sizes (&rdwr_idx, fndecl, fntype, exp); - - /* Check calls to operator new for mismatched forms and attempts - to deallocate unallocated objects. */ - maybe_emit_free_warning (exp); } /* Update ARGS_SIZE to contain the total size for the argument block. diff --git a/gcc/cp/init.c b/gcc/cp/init.c index d47e405e7459..229c84e1d74d 100644 --- a/gcc/cp/init.c +++ b/gcc/cp/init.c @@ -34,7 +34,7 @@ along with GCC; see the file COPYING3. If not see #include "attribs.h" #include "asan.h" #include "stor-layout.h" -#include "builtins.h" +#include "pointer-query.h" static bool begin_init_stmts (tree *, tree *); static tree finish_init_stmts (bool, tree, tree); diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc index 598c76bf52e9..3a5c2dd0be88 100644 --- a/gcc/gimple-array-bounds.cc +++ b/gcc/gimple-array-bounds.cc @@ -37,7 +37,7 @@ along with GCC; see the file COPYING3. If not see #include "domwalk.h" #include "tree-cfg.h" #include "attribs.h" -#include "builtins.h" +#include "pointer-query.h" // This purposely returns a value_range, not a value_range_equiv, to // break the dependency on equivalences for this pass. diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c index b6f605435438..ad7b140173fa 100644 --- a/gcc/gimple-fold.c +++ b/gcc/gimple-fold.c @@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see #include "ssa.h" #include "cgraph.h" #include "gimple-pretty-print.h" +#include "gimple-ssa-warn-access.h" #include "gimple-ssa-warn-restrict.h" #include "fold-const.h" #include "stmt.h" diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index f38fb03f068e..8e90b7cfc43c 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -71,6 +71,7 @@ along with GCC; see the file COPYING3. If not see #include "attribs.h" #include "builtins.h" +#include "pointer-query.h" #include "stor-layout.h" #include "realmpfr.h" diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc new file mode 100644 index 000000000000..e4d98b2ec284 --- /dev/null +++ b/gcc/gimple-ssa-warn-access.cc @@ -0,0 +1,1765 @@ +/* Pass to detect and issue warnings for invalid accesses, including + invalid or mismatched allocation/deallocation calls. + + Copyright (C) 2020-2021 Free Software Foundation, Inc. + Contributed by Martin Sebor . + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 3, or (at your option) any later + version. + + GCC is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + . */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "tree-pass.h" +#include "builtins.h" +#include "ssa.h" +#include "gimple-pretty-print.h" +#include "gimple-ssa-warn-access.h" +#include "gimple-ssa-warn-restrict.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "gimple-fold.h" +#include "gimple-iterator.h" +#include "tree-dfa.h" +#include "tree-ssa.h" +#include "tree-cfg.h" +#include "tree-object-size.h" +#include "calls.h" +#include "cfgloop.h" +#include "intl.h" +#include "gimple-range.h" +#include "stringpool.h" +#include "attribs.h" +#include "demangle.h" +#include "pointer-query.h" + +/* 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 */) +{ + const opt_code opt = OPT_Wstringop_overread; + if ((expr && warning_suppressed_p (expr, opt)) + || warning_suppressed_p (arg, opt)) + 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, + "%qD specified bound %s exceeds " + "maximum object size %E", + func, bndstr, maxobjsize); + else + { + bool maybe = wi::to_wide (size) == bndrng[0]; + warned = warning_at (loc, opt, + exact + ? G_("%qD specified bound %s exceeds " + "the size %E of unterminated array") + : (maybe + ? G_("%qD specified bound %s may " + "exceed the size of at most %E " + "of unterminated array") + : G_("%qD specified bound %s exceeds " + "the size of at most %E " + "of unterminated array")), + func, bndstr, size); + } + } + else + warned = warning_at (loc, opt, + "%qD argument missing terminating nul", + func); + } + else + { + if (bndrng) + { + if (wi::ltu_p (maxsiz, bndrng[0])) + warned = warning_at (loc, opt, + "%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, + 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, + "%qs argument missing terminating nul", + fname); + } + + if (warned) + { + inform (DECL_SOURCE_LOCATION (decl), + "referenced argument declared here"); + suppress_warning (arg, opt); + if (expr) + suppress_warning (expr, opt); + } +} + +/* 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) + { + value_range r; + + get_global_range_query ()->range_of_expr (r, bound); + + if (r.kind () != VR_RANGE) + return true; + + bndrng[0] = r.lower_bound (); + bndrng[1] = r.upper_bound (); + + 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; +} + +/* Issue a warning OPT for a bounded call EXP with a bound in RANGE + accessing an object with SIZE. */ + +bool +maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func, + tree bndrng[2], tree size, + const access_data *pad /* = NULL */) +{ + if (!bndrng[0] || warning_suppressed_p (exp, opt)) + return false; + + tree maxobjsize = max_object_size (); + + bool warned = false; + + if (opt == OPT_Wstringop_overread) + { + bool maybe = pad && pad->src.phi (); + + if (tree_int_cst_lt (maxobjsize, bndrng[0])) + { + if (bndrng[0] == bndrng[1]) + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound %E may " + "exceed maximum object size %E") + : G_("%qD specified bound %E " + "exceeds maximum object size %E")), + func, bndrng[0], maxobjsize) + : warning_at (loc, opt, + (maybe + ? G_("specified bound %E may " + "exceed maximum object size %E") + : G_("specified bound %E " + "exceeds maximum object size %E")), + bndrng[0], maxobjsize)); + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound [%E, %E] may " + "exceed maximum object size %E") + : G_("%qD specified bound [%E, %E] " + "exceeds maximum object size %E")), + func, + bndrng[0], bndrng[1], maxobjsize) + : warning_at (loc, opt, + (maybe + ? G_("specified bound [%E, %E] may " + "exceed maximum object size %E") + : G_("specified bound [%E, %E] " + "exceeds maximum object size %E")), + bndrng[0], bndrng[1], maxobjsize)); + } + else if (!size || tree_int_cst_le (bndrng[0], size)) + return false; + else if (tree_int_cst_equal (bndrng[0], bndrng[1])) + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound %E may exceed " + "source size %E") + : G_("%qD specified bound %E exceeds " + "source size %E")), + func, bndrng[0], size) + : warning_at (loc, opt, + (maybe + ? G_("specified bound %E may exceed " + "source size %E") + : G_("specified bound %E exceeds " + "source size %E")), + bndrng[0], size)); + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound [%E, %E] may " + "exceed source size %E") + : G_("%qD specified bound [%E, %E] exceeds " + "source size %E")), + func, bndrng[0], bndrng[1], size) + : warning_at (loc, opt, + (maybe + ? G_("specified bound [%E, %E] may exceed " + "source size %E") + : G_("specified bound [%E, %E] exceeds " + "source size %E")), + bndrng[0], bndrng[1], size)); + if (warned) + { + if (pad && pad->src.ref) + { + if (DECL_P (pad->src.ref)) + inform (DECL_SOURCE_LOCATION (pad->src.ref), + "source object declared here"); + else if (EXPR_HAS_LOCATION (pad->src.ref)) + inform (EXPR_LOCATION (pad->src.ref), + "source object allocated here"); + } + suppress_warning (exp, opt); + } + + return warned; + } + + bool maybe = pad && pad->dst.phi (); + if (tree_int_cst_lt (maxobjsize, bndrng[0])) + { + if (bndrng[0] == bndrng[1]) + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified size %E may " + "exceed maximum object size %E") + : G_("%qD specified size %E " + "exceeds maximum object size %E")), + func, bndrng[0], maxobjsize) + : warning_at (loc, opt, + (maybe + ? G_("specified size %E may exceed " + "maximum object size %E") + : G_("specified size %E exceeds " + "maximum object size %E")), + bndrng[0], maxobjsize)); + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified size between %E and %E " + "may exceed maximum object size %E") + : G_("%qD specified size between %E and %E " + "exceeds maximum object size %E")), + func, bndrng[0], bndrng[1], maxobjsize) + : warning_at (loc, opt, + (maybe + ? G_("specified size between %E and %E " + "may exceed maximum object size %E") + : G_("specified size between %E and %E " + "exceeds maximum object size %E")), + bndrng[0], bndrng[1], maxobjsize)); + } + else if (!size || tree_int_cst_le (bndrng[0], size)) + return false; + else if (tree_int_cst_equal (bndrng[0], bndrng[1])) + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound %E may exceed " + "destination size %E") + : G_("%qD specified bound %E exceeds " + "destination size %E")), + func, bndrng[0], size) + : warning_at (loc, opt, + (maybe + ? G_("specified bound %E may exceed " + "destination size %E") + : G_("specified bound %E exceeds " + "destination size %E")), + bndrng[0], size)); + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD specified bound [%E, %E] may exceed " + "destination size %E") + : G_("%qD specified bound [%E, %E] exceeds " + "destination size %E")), + func, bndrng[0], bndrng[1], size) + : warning_at (loc, opt, + (maybe + ? G_("specified bound [%E, %E] exceeds " + "destination size %E") + : G_("specified bound [%E, %E] exceeds " + "destination size %E")), + bndrng[0], bndrng[1], size)); + + if (warned) + { + if (pad && pad->dst.ref) + { + if (DECL_P (pad->dst.ref)) + inform (DECL_SOURCE_LOCATION (pad->dst.ref), + "destination object declared here"); + else if (EXPR_HAS_LOCATION (pad->dst.ref)) + inform (EXPR_LOCATION (pad->dst.ref), + "destination object allocated here"); + } + suppress_warning (exp, opt); + } + + return warned; +} + +/* For an expression EXP issue an access warning controlled by option OPT + with access to a region SIZE bytes in size in the RANGE of sizes. + WRITE is true for a write access, READ for a read access, neither for + call that may or may not perform an access but for which the range + is expected to valid. + Returns true when a warning has been issued. */ + +static bool +warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2], + tree size, bool write, bool read, bool maybe) +{ + bool warned = false; + + if (write && read) + { + if (tree_int_cst_equal (range[0], range[1])) + warned = (func + ? warning_n (loc, opt, tree_to_uhwi (range[0]), + (maybe + ? G_("%qD may access %E byte in a region " + "of size %E") + : G_("%qD accessing %E byte in a region " + "of size %E")), + (maybe + ? G_ ("%qD may access %E bytes in a region " + "of size %E") + : G_ ("%qD accessing %E bytes in a region " + "of size %E")), + func, range[0], size) + : warning_n (loc, opt, tree_to_uhwi (range[0]), + (maybe + ? G_("may access %E byte in a region " + "of size %E") + : G_("accessing %E byte in a region " + "of size %E")), + (maybe + ? G_("may access %E bytes in a region " + "of size %E") + : G_("accessing %E bytes in a region " + "of size %E")), + range[0], size)); + else if (tree_int_cst_sign_bit (range[1])) + { + /* Avoid printing the upper bound if it's invalid. */ + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD may access %E or more bytes " + "in a region of size %E") + : G_("%qD accessing %E or more bytes " + "in a region of size %E")), + func, range[0], size) + : warning_at (loc, opt, + (maybe + ? G_("may access %E or more bytes " + "in a region of size %E") + : G_("accessing %E or more bytes " + "in a region of size %E")), + range[0], size)); + } + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD may access between %E and %E " + "bytes in a region of size %E") + : G_("%qD accessing between %E and %E " + "bytes in a region of size %E")), + func, range[0], range[1], size) + : warning_at (loc, opt, + (maybe + ? G_("may access between %E and %E bytes " + "in a region of size %E") + : G_("accessing between %E and %E bytes " + "in a region of size %E")), + range[0], range[1], size)); + return warned; + } + + if (write) + { + if (tree_int_cst_equal (range[0], range[1])) + warned = (func + ? warning_n (loc, opt, tree_to_uhwi (range[0]), + (maybe + ? G_("%qD may write %E byte into a region " + "of size %E") + : G_("%qD writing %E byte into a region " + "of size %E overflows the destination")), + (maybe + ? G_("%qD may write %E bytes into a region " + "of size %E") + : G_("%qD writing %E bytes into a region " + "of size %E overflows the destination")), + func, range[0], size) + : warning_n (loc, opt, tree_to_uhwi (range[0]), + (maybe + ? G_("may write %E byte into a region " + "of size %E") + : G_("writing %E byte into a region " + "of size %E overflows the destination")), + (maybe + ? G_("may write %E bytes into a region " + "of size %E") + : G_("writing %E bytes into a region " + "of size %E overflows the destination")), + range[0], size)); + else if (tree_int_cst_sign_bit (range[1])) + { + /* Avoid printing the upper bound if it's invalid. */ + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD may write %E or more bytes " + "into a region of size %E") + : G_("%qD writing %E or more bytes " + "into a region of size %E overflows " + "the destination")), + func, range[0], size) + : warning_at (loc, opt, + (maybe + ? G_("may write %E or more bytes into " + "a region of size %E") + : G_("writing %E or more bytes into " + "a region of size %E overflows " + "the destination")), + range[0], size)); + } + else + warned = (func + ? warning_at (loc, opt, + (maybe + ? G_("%qD may write between %E and %E bytes " + "into a region of size %E") + : G_("%qD writing between %E and %E bytes " + "into a region of size %E overflows " + "the destination")), + func, range[0], range[1], size) + : warning_at (loc, opt, + (maybe + ? G_("may write between %E and %E bytes " + "into a region of size %E") + : G_("writing between %E and %E bytes " + "into a region of size %E overflows " + "the destination")), + range[0], range[1], size)); + return warned; + } + + if (read) + { + if (tree_int_cst_equal (range[0], range[1])) + warned = (func + ? warning_n (loc, OPT_Wstringop_overread, + tree_to_uhwi (range[0]), + (maybe + ? G_("%qD may read %E byte from a region " + "of size %E") + : G_("%qD reading %E byte from a region " + "of size %E")), + (maybe + ? G_("%qD may read %E bytes from a region " + "of size %E") + : G_("%qD reading %E bytes from a region " + "of size %E")), + func, range[0], size) + : warning_n (loc, OPT_Wstringop_overread, + tree_to_uhwi (range[0]), + (maybe + ? G_("may read %E byte from a region " + "of size %E") + : G_("reading %E byte from a region " + "of size %E")), + (maybe + ? G_("may read %E bytes from a region " + "of size %E") + : G_("reading %E bytes from a region " + "of size %E")), + range[0], size)); + else if (tree_int_cst_sign_bit (range[1])) + { + /* Avoid printing the upper bound if it's invalid. */ + warned = (func + ? warning_at (loc, OPT_Wstringop_overread, + (maybe + ? G_("%qD may read %E or more bytes " + "from a region of size %E") + : G_("%qD reading %E or more bytes " + "from a region of size %E")), + func, range[0], size) + : warning_at (loc, OPT_Wstringop_overread, + (maybe + ? G_("may read %E or more bytes " + "from a region of size %E") + : G_("reading %E or more bytes " + "from a region of size %E")), + range[0], size)); + } + else + warned = (func + ? warning_at (loc, OPT_Wstringop_overread, + (maybe + ? G_("%qD may read between %E and %E bytes " + "from a region of size %E") + : G_("%qD reading between %E and %E bytes " + "from a region of size %E")), + func, range[0], range[1], size) + : warning_at (loc, opt, + (maybe + ? G_("may read between %E and %E bytes " + "from a region of size %E") + : G_("reading between %E and %E bytes " + "from a region of size %E")), + range[0], range[1], size)); + + if (warned) + suppress_warning (exp, OPT_Wstringop_overread); + + return warned; + } + + if (tree_int_cst_equal (range[0], range[1]) + || tree_int_cst_sign_bit (range[1])) + warned = (func + ? warning_n (loc, OPT_Wstringop_overread, + tree_to_uhwi (range[0]), + "%qD expecting %E byte in a region of size %E", + "%qD expecting %E bytes in a region of size %E", + func, range[0], size) + : warning_n (loc, OPT_Wstringop_overread, + tree_to_uhwi (range[0]), + "expecting %E byte in a region of size %E", + "expecting %E bytes in a region of size %E", + range[0], size)); + else if (tree_int_cst_sign_bit (range[1])) + { + /* Avoid printing the upper bound if it's invalid. */ + warned = (func + ? warning_at (loc, OPT_Wstringop_overread, + "%qD expecting %E or more bytes in a region " + "of size %E", + func, range[0], size) + : warning_at (loc, OPT_Wstringop_overread, + "expecting %E or more bytes in a region " + "of size %E", + range[0], size)); + } + else + warned = (func + ? warning_at (loc, OPT_Wstringop_overread, + "%qD expecting between %E and %E bytes in " + "a region of size %E", + func, range[0], range[1], size) + : warning_at (loc, OPT_Wstringop_overread, + "expecting between %E and %E bytes in " + "a region of size %E", + range[0], range[1], size)); + + if (warned) + suppress_warning (exp, OPT_Wstringop_overread); + + return warned; +} + +/* Helper to set RANGE to the range of BOUND if it's nonnull, bounded + by BNDRNG if nonnull and valid. */ + +void +get_size_range (tree bound, tree range[2], const offset_int bndrng[2]) +{ + if (bound) + get_size_range (bound, range); + + if (!bndrng || (bndrng[0] == 0 && bndrng[1] == HOST_WIDE_INT_M1U)) + return; + + if (range[0] && TREE_CODE (range[0]) == INTEGER_CST) + { + offset_int r[] = + { wi::to_offset (range[0]), wi::to_offset (range[1]) }; + if (r[0] < bndrng[0]) + range[0] = wide_int_to_tree (sizetype, bndrng[0]); + if (bndrng[1] < r[1]) + range[1] = wide_int_to_tree (sizetype, bndrng[1]); + } + else + { + range[0] = wide_int_to_tree (sizetype, bndrng[0]); + range[1] = wide_int_to_tree (sizetype, bndrng[1]); + } +} + +/* Try to verify that the sizes and lengths of the arguments to a string + manipulation function given by EXP are within valid bounds and that + the operation does not lead to buffer overflow or read past the end. + Arguments other than EXP may be null. When non-null, the arguments + have the following meaning: + DST is the destination of a copy call or NULL otherwise. + SRC is the source of a copy call or NULL otherwise. + DSTWRITE is the number of bytes written into the destination obtained + from the user-supplied size argument to the function (such as in + memcpy(DST, SRCs, DSTWRITE) or strncpy(DST, DRC, DSTWRITE). + MAXREAD is the user-supplied bound on the length of the source sequence + (such as in strncat(d, s, N). It specifies the upper limit on the number + of bytes to write. If NULL, it's taken to be the same as DSTWRITE. + SRCSTR is the source string (such as in strcpy(DST, SRC)) when the + expression EXP is a string function call (as opposed to a memory call + like memcpy). As an exception, SRCSTR can also be an integer denoting + the precomputed size of the source string or object (for functions like + memcpy). + DSTSIZE is the size of the destination object. + + When DSTWRITE is null LEN is checked to verify that it doesn't exceed + SIZE_MAX. + + WRITE is true for write accesses, READ is true for reads. Both are + false for simple size checks in calls to functions that neither read + from nor write to the region. + + When nonnull, PAD points to a more detailed description of the access. + + If the call is successfully verified as safe return true, otherwise + return false. */ + +bool +check_access (tree exp, tree dstwrite, + tree maxread, tree srcstr, tree dstsize, + access_mode mode, const access_data *pad /* = NULL */) +{ + /* The size of the largest object is half the address space, or + PTRDIFF_MAX. (This is way too permissive.) */ + tree maxobjsize = max_object_size (); + + /* Either an approximate/minimum the length of the source string for + string functions or the size of the source object for raw memory + functions. */ + tree slen = NULL_TREE; + + /* The range of the access in bytes; first set to the write access + for functions that write and then read for those that also (or + just) read. */ + tree range[2] = { NULL_TREE, NULL_TREE }; + + /* Set to true when the exact number of bytes written by a string + function like strcpy is not known and the only thing that is + known is that it must be at least one (for the terminating nul). */ + bool at_least_one = false; + if (srcstr) + { + /* SRCSTR is normally a pointer to string but as a special case + it can be an integer denoting the length of a string. */ + if (POINTER_TYPE_P (TREE_TYPE (srcstr))) + { + if (!check_nul_terminated_array (exp, srcstr, maxread)) + return false; + /* Try to determine the range of lengths the source string + refers to. If it can be determined and is less than + the upper bound given by MAXREAD add one to it for + the terminating nul. Otherwise, set it to one for + the same reason, or to MAXREAD as appropriate. */ + c_strlen_data lendata = { }; + get_range_strlen (srcstr, &lendata, /* eltsize = */ 1); + range[0] = lendata.minlen; + range[1] = lendata.maxbound ? lendata.maxbound : lendata.maxlen; + if (range[0] + && TREE_CODE (range[0]) == INTEGER_CST + && TREE_CODE (range[1]) == INTEGER_CST + && (!maxread || TREE_CODE (maxread) == INTEGER_CST)) + { + if (maxread && tree_int_cst_le (maxread, range[0])) + range[0] = range[1] = maxread; + else + range[0] = fold_build2 (PLUS_EXPR, size_type_node, + range[0], size_one_node); + + if (maxread && tree_int_cst_le (maxread, range[1])) + range[1] = maxread; + else if (!integer_all_onesp (range[1])) + range[1] = fold_build2 (PLUS_EXPR, size_type_node, + range[1], size_one_node); + + slen = range[0]; + } + else + { + at_least_one = true; + slen = size_one_node; + } + } + else + slen = srcstr; + } + + if (!dstwrite && !maxread) + { + /* When the only available piece of data is the object size + there is nothing to do. */ + if (!slen) + return true; + + /* Otherwise, when the length of the source sequence is known + (as with strlen), set DSTWRITE to it. */ + if (!range[0]) + dstwrite = slen; + } + + if (!dstsize) + dstsize = maxobjsize; + + /* Set RANGE to that of DSTWRITE if non-null, bounded by PAD->DST.BNDRNG + if valid. */ + get_size_range (dstwrite, range, pad ? pad->dst.bndrng : NULL); + + tree func = get_callee_fndecl (exp); + /* Read vs write access by built-ins can be determined from the const + qualifiers on the pointer argument. In the absence of attribute + access, non-const qualified pointer arguments to user-defined + functions are assumed to both read and write the objects. */ + const bool builtin = func ? fndecl_built_in_p (func) : false; + + /* First check the number of bytes to be written against the maximum + object size. */ + if (range[0] + && TREE_CODE (range[0]) == INTEGER_CST + && tree_int_cst_lt (maxobjsize, range[0])) + { + location_t loc = EXPR_LOCATION (exp); + maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range, + NULL_TREE, pad); + return false; + } + + /* The number of bytes to write is "exact" if DSTWRITE is non-null, + constant, and in range of unsigned HOST_WIDE_INT. */ + bool exactwrite = dstwrite && tree_fits_uhwi_p (dstwrite); + + /* Next check the number of bytes to be written against the destination + object size. */ + if (range[0] || !exactwrite || integer_all_onesp (dstwrite)) + { + if (range[0] + && TREE_CODE (range[0]) == INTEGER_CST + && ((tree_fits_uhwi_p (dstsize) + && tree_int_cst_lt (dstsize, range[0])) + || (dstwrite + && tree_fits_uhwi_p (dstwrite) + && tree_int_cst_lt (dstwrite, range[0])))) + { + const opt_code opt = OPT_Wstringop_overflow_; + if (warning_suppressed_p (exp, opt) + || (pad && pad->dst.ref + && warning_suppressed_p (pad->dst.ref, opt))) + return false; + + location_t loc = EXPR_LOCATION (exp); + bool warned = false; + if (dstwrite == slen && at_least_one) + { + /* This is a call to strcpy with a destination of 0 size + and a source of unknown length. The call will write + at least one byte past the end of the destination. */ + warned = (func + ? warning_at (loc, opt, + "%qD writing %E or more bytes into " + "a region of size %E overflows " + "the destination", + func, range[0], dstsize) + : warning_at (loc, opt, + "writing %E or more bytes into " + "a region of size %E overflows " + "the destination", + range[0], dstsize)); + } + else + { + const bool read + = mode == access_read_only || mode == access_read_write; + const bool write + = mode == access_write_only || mode == access_read_write; + const bool maybe = pad && pad->dst.parmarray; + warned = warn_for_access (loc, func, exp, + OPT_Wstringop_overflow_, + range, dstsize, + write, read && !builtin, maybe); + } + + if (warned) + { + suppress_warning (exp, OPT_Wstringop_overflow_); + if (pad) + pad->dst.inform_access (pad->mode); + } + + /* Return error when an overflow has been detected. */ + return false; + } + } + + /* Check the maximum length of the source sequence against the size + of the destination object if known, or against the maximum size + of an object. */ + if (maxread) + { + /* Set RANGE to that of MAXREAD, bounded by PAD->SRC.BNDRNG if + PAD is nonnull and BNDRNG is valid. */ + get_size_range (maxread, range, pad ? pad->src.bndrng : NULL); + + location_t loc = EXPR_LOCATION (exp); + tree size = dstsize; + if (pad && pad->mode == access_read_only) + size = wide_int_to_tree (sizetype, pad->src.sizrng[1]); + + if (range[0] && maxread && tree_fits_uhwi_p (size)) + { + if (tree_int_cst_lt (maxobjsize, range[0])) + { + maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func, + range, size, pad); + return false; + } + + if (size != maxobjsize && tree_int_cst_lt (size, range[0])) + { + opt_code opt = (dstwrite || mode != access_read_only + ? OPT_Wstringop_overflow_ + : OPT_Wstringop_overread); + maybe_warn_for_bound (opt, loc, exp, func, range, size, pad); + return false; + } + } + + maybe_warn_nonstring_arg (func, exp); + } + + /* Check for reading past the end of SRC. */ + bool overread = (slen + && slen == srcstr + && dstwrite + && range[0] + && TREE_CODE (slen) == INTEGER_CST + && tree_int_cst_lt (slen, range[0])); + /* If none is determined try to get a better answer based on the details + in PAD. */ + if (!overread + && pad + && pad->src.sizrng[1] >= 0 + && pad->src.offrng[0] >= 0 + && (pad->src.offrng[1] < 0 + || pad->src.offrng[0] <= pad->src.offrng[1])) + { + /* Set RANGE to that of MAXREAD, bounded by PAD->SRC.BNDRNG if + PAD is nonnull and BNDRNG is valid. */ + get_size_range (maxread, range, pad ? pad->src.bndrng : NULL); + /* Set OVERREAD for reads starting just past the end of an object. */ + overread = pad->src.sizrng[1] - pad->src.offrng[0] < pad->src.bndrng[0]; + range[0] = wide_int_to_tree (sizetype, pad->src.bndrng[0]); + slen = size_zero_node; + } + + if (overread) + { + const opt_code opt = OPT_Wstringop_overread; + if (warning_suppressed_p (exp, opt) + || (srcstr && warning_suppressed_p (srcstr, opt)) + || (pad && pad->src.ref + && warning_suppressed_p (pad->src.ref, opt))) + return false; + + location_t loc = EXPR_LOCATION (exp); + const bool read + = mode == access_read_only || mode == access_read_write; + const bool maybe = pad && pad->dst.parmarray; + if (warn_for_access (loc, func, exp, opt, range, slen, false, read, + maybe)) + { + suppress_warning (exp, opt); + if (pad) + pad->src.inform_access (access_read_only); + } + return false; + } + + return true; +} + +/* Return true if STMT is a call to an allocation function. Unless + ALL_ALLOC is set, consider only functions that return dynmamically + allocated objects. Otherwise return true even for all forms of + alloca (including VLA). */ + +static bool +fndecl_alloc_p (tree fndecl, bool all_alloc) +{ + if (!fndecl) + return false; + + /* A call to operator new isn't recognized as one to a built-in. */ + if (DECL_IS_OPERATOR_NEW_P (fndecl)) + return true; + + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + return all_alloc; + case BUILT_IN_ALIGNED_ALLOC: + case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + return true; + default: + break; + } + } + + /* A function is considered an allocation function if it's declared + with attribute malloc with an argument naming its associated + deallocation function. */ + tree attrs = DECL_ATTRIBUTES (fndecl); + if (!attrs) + return false; + + for (tree allocs = attrs; + (allocs = lookup_attribute ("malloc", allocs)); + allocs = TREE_CHAIN (allocs)) + { + tree args = TREE_VALUE (allocs); + if (!args) + continue; + + if (TREE_VALUE (args)) + return true; + } + + return false; +} + +/* Return true if STMT is a call to an allocation function. A wrapper + around fndecl_alloc_p. */ + +static bool +gimple_call_alloc_p (gimple *stmt, bool all_alloc = false) +{ + return fndecl_alloc_p (gimple_call_fndecl (stmt), all_alloc); +} + +/* Return true if DELC doesn't refer to an operator delete that's + suitable to call with a pointer returned from the operator new + described by NEWC. */ + +static bool +new_delete_mismatch_p (const demangle_component &newc, + const demangle_component &delc) +{ + if (newc.type != delc.type) + return true; + + switch (newc.type) + { + case DEMANGLE_COMPONENT_NAME: + { + int len = newc.u.s_name.len; + const char *news = newc.u.s_name.s; + const char *dels = delc.u.s_name.s; + if (len != delc.u.s_name.len || memcmp (news, dels, len)) + return true; + + if (news[len] == 'n') + { + if (news[len + 1] == 'a') + return dels[len] != 'd' || dels[len + 1] != 'a'; + if (news[len + 1] == 'w') + return dels[len] != 'd' || dels[len + 1] != 'l'; + } + return false; + } + + case DEMANGLE_COMPONENT_OPERATOR: + /* Operator mismatches are handled above. */ + return false; + + case DEMANGLE_COMPONENT_EXTENDED_OPERATOR: + if (newc.u.s_extended_operator.args != delc.u.s_extended_operator.args) + return true; + return new_delete_mismatch_p (*newc.u.s_extended_operator.name, + *delc.u.s_extended_operator.name); + + case DEMANGLE_COMPONENT_FIXED_TYPE: + if (newc.u.s_fixed.accum != delc.u.s_fixed.accum + || newc.u.s_fixed.sat != delc.u.s_fixed.sat) + return true; + return new_delete_mismatch_p (*newc.u.s_fixed.length, + *delc.u.s_fixed.length); + + case DEMANGLE_COMPONENT_CTOR: + if (newc.u.s_ctor.kind != delc.u.s_ctor.kind) + return true; + return new_delete_mismatch_p (*newc.u.s_ctor.name, + *delc.u.s_ctor.name); + + case DEMANGLE_COMPONENT_DTOR: + if (newc.u.s_dtor.kind != delc.u.s_dtor.kind) + return true; + return new_delete_mismatch_p (*newc.u.s_dtor.name, + *delc.u.s_dtor.name); + + case DEMANGLE_COMPONENT_BUILTIN_TYPE: + { + /* The demangler API provides no better way to compare built-in + types except to by comparing their demangled names. */ + size_t nsz, dsz; + demangle_component *pnc = const_cast(&newc); + demangle_component *pdc = const_cast(&delc); + char *nts = cplus_demangle_print (0, pnc, 16, &nsz); + char *dts = cplus_demangle_print (0, pdc, 16, &dsz); + if (!nts != !dts) + return true; + bool mismatch = strcmp (nts, dts); + free (nts); + free (dts); + return mismatch; + } + + case DEMANGLE_COMPONENT_SUB_STD: + if (newc.u.s_string.len != delc.u.s_string.len) + return true; + return memcmp (newc.u.s_string.string, delc.u.s_string.string, + newc.u.s_string.len); + + case DEMANGLE_COMPONENT_FUNCTION_PARAM: + case DEMANGLE_COMPONENT_TEMPLATE_PARAM: + return newc.u.s_number.number != delc.u.s_number.number; + + case DEMANGLE_COMPONENT_CHARACTER: + return newc.u.s_character.character != delc.u.s_character.character; + + case DEMANGLE_COMPONENT_DEFAULT_ARG: + case DEMANGLE_COMPONENT_LAMBDA: + if (newc.u.s_unary_num.num != delc.u.s_unary_num.num) + return true; + return new_delete_mismatch_p (*newc.u.s_unary_num.sub, + *delc.u.s_unary_num.sub); + default: + break; + } + + if (!newc.u.s_binary.left != !delc.u.s_binary.left) + return true; + + if (!newc.u.s_binary.left) + return false; + + if (new_delete_mismatch_p (*newc.u.s_binary.left, *delc.u.s_binary.left) + || !newc.u.s_binary.right != !delc.u.s_binary.right) + return true; + + if (newc.u.s_binary.right) + return new_delete_mismatch_p (*newc.u.s_binary.right, + *delc.u.s_binary.right); + return false; +} + +/* Return true if DELETE_DECL is an operator delete that's not suitable + to call with a pointer returned fron NEW_DECL. */ + +static bool +new_delete_mismatch_p (tree new_decl, tree delete_decl) +{ + tree new_name = DECL_ASSEMBLER_NAME (new_decl); + tree delete_name = DECL_ASSEMBLER_NAME (delete_decl); + + /* valid_new_delete_pair_p() returns a conservative result (currently + it only handles global operators). A true result is reliable but + a false result doesn't necessarily mean the operators don't match. */ + if (valid_new_delete_pair_p (new_name, delete_name)) + return false; + + /* For anything not handled by valid_new_delete_pair_p() such as member + operators compare the individual demangled components of the mangled + name. */ + const char *new_str = IDENTIFIER_POINTER (new_name); + const char *del_str = IDENTIFIER_POINTER (delete_name); + + void *np = NULL, *dp = NULL; + demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np); + demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp); + bool mismatch = new_delete_mismatch_p (*ndc, *ddc); + free (np); + free (dp); + return mismatch; +} + +/* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation + functions. Return true if the latter is suitable to deallocate objects + allocated by calls to the former. */ + +static bool +matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) +{ + /* Set to alloc_kind_t::builtin if ALLOC_DECL is associated with + a built-in deallocator. */ + enum class alloc_kind_t { none, builtin, user } + alloc_dealloc_kind = alloc_kind_t::none; + + if (DECL_IS_OPERATOR_NEW_P (alloc_decl)) + { + if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) + /* Return true iff both functions are of the same array or + singleton form and false otherwise. */ + return !new_delete_mismatch_p (alloc_decl, dealloc_decl); + + /* Return false for deallocation functions that are known not + to match. */ + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) + || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) + return false; + /* Otherwise proceed below to check the deallocation function's + "*dealloc" attributes to look for one that mentions this operator + new. */ + } + else if (fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (alloc_decl)) + { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + return false; + + case BUILT_IN_ALIGNED_ALLOC: + case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) + return false; + + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) + || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) + return true; + + alloc_dealloc_kind = alloc_kind_t::builtin; + break; + + default: + break; + } + } + + /* Set if DEALLOC_DECL both allocates and deallocates. */ + alloc_kind_t realloc_kind = alloc_kind_t::none; + + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL)) + { + built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl); + if (dealloc_code == BUILT_IN_REALLOC) + realloc_kind = alloc_kind_t::builtin; + + for (tree amats = DECL_ATTRIBUTES (alloc_decl); + (amats = lookup_attribute ("malloc", amats)); + amats = TREE_CHAIN (amats)) + { + tree args = TREE_VALUE (amats); + if (!args) + continue; + + tree fndecl = TREE_VALUE (args); + if (!fndecl || !DECL_P (fndecl)) + continue; + + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL) + && dealloc_code == DECL_FUNCTION_CODE (fndecl)) + return true; + } + } + + const bool alloc_builtin = fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL); + alloc_kind_t realloc_dealloc_kind = alloc_kind_t::none; + + /* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list + of its associated allocation functions for ALLOC_DECL. + If the corresponding ALLOC_DECL is found they're a matching pair, + otherwise they're not. + With DDATS set to the Deallocator's *Dealloc ATtributes... */ + for (tree ddats = DECL_ATTRIBUTES (dealloc_decl); + (ddats = lookup_attribute ("*dealloc", ddats)); + ddats = TREE_CHAIN (ddats)) + { + tree args = TREE_VALUE (ddats); + if (!args) + continue; + + tree alloc = TREE_VALUE (args); + if (!alloc) + continue; + + if (alloc == DECL_NAME (dealloc_decl)) + realloc_kind = alloc_kind_t::user; + + if (DECL_P (alloc)) + { + gcc_checking_assert (fndecl_built_in_p (alloc, BUILT_IN_NORMAL)); + + switch (DECL_FUNCTION_CODE (alloc)) + { + case BUILT_IN_ALIGNED_ALLOC: + case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + realloc_dealloc_kind = alloc_kind_t::builtin; + break; + default: + break; + } + + if (!alloc_builtin) + continue; + + if (DECL_FUNCTION_CODE (alloc) != DECL_FUNCTION_CODE (alloc_decl)) + continue; + + return true; + } + + if (alloc == DECL_NAME (alloc_decl)) + return true; + } + + if (realloc_kind == alloc_kind_t::none) + return false; + + hash_set common_deallocs; + /* Special handling for deallocators. Iterate over both the allocator's + and the reallocator's associated deallocator functions looking for + the first one in common. If one is found, the de/reallocator is + a match for the allocator even though the latter isn't directly + associated with the former. This simplifies declarations in system + headers. + With AMATS set to the Allocator's Malloc ATtributes, + and RMATS set to Reallocator's Malloc ATtributes... */ + for (tree amats = DECL_ATTRIBUTES (alloc_decl), + rmats = DECL_ATTRIBUTES (dealloc_decl); + (amats = lookup_attribute ("malloc", amats)) + || (rmats = lookup_attribute ("malloc", rmats)); + amats = amats ? TREE_CHAIN (amats) : NULL_TREE, + rmats = rmats ? TREE_CHAIN (rmats) : NULL_TREE) + { + if (tree args = amats ? TREE_VALUE (amats) : NULL_TREE) + if (tree adealloc = TREE_VALUE (args)) + { + if (DECL_P (adealloc) + && fndecl_built_in_p (adealloc, BUILT_IN_NORMAL)) + { + built_in_function fncode = DECL_FUNCTION_CODE (adealloc); + if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) + { + if (realloc_kind == alloc_kind_t::builtin) + return true; + alloc_dealloc_kind = alloc_kind_t::builtin; + } + continue; + } + + common_deallocs.add (adealloc); + } + + if (tree args = rmats ? TREE_VALUE (rmats) : NULL_TREE) + if (tree ddealloc = TREE_VALUE (args)) + { + if (DECL_P (ddealloc) + && fndecl_built_in_p (ddealloc, BUILT_IN_NORMAL)) + { + built_in_function fncode = DECL_FUNCTION_CODE (ddealloc); + if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) + { + if (alloc_dealloc_kind == alloc_kind_t::builtin) + return true; + realloc_dealloc_kind = alloc_kind_t::builtin; + } + continue; + } + + if (common_deallocs.add (ddealloc)) + return true; + } + } + + /* Succeed only if ALLOC_DECL and the reallocator DEALLOC_DECL share + a built-in deallocator. */ + return (alloc_dealloc_kind == alloc_kind_t::builtin + && realloc_dealloc_kind == alloc_kind_t::builtin); +} + +/* Return true if DEALLOC_DECL is a function suitable to deallocate + objectes allocated by the ALLOC call. */ + +static bool +matching_alloc_calls_p (gimple *alloc, tree dealloc_decl) +{ + tree alloc_decl = gimple_call_fndecl (alloc); + if (!alloc_decl) + return true; + + return matching_alloc_calls_p (alloc_decl, dealloc_decl); +} + +/* Diagnose a call EXP to deallocate a pointer referenced by AREF if it + includes a nonzero offset. Such a pointer cannot refer to the beginning + of an allocated object. A negative offset may refer to it only if + the target pointer is unknown. */ + +static bool +warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref) +{ + if (aref.deref || aref.offrng[0] <= 0 || aref.offrng[1] <= 0) + return false; + + tree dealloc_decl = gimple_call_fndecl (call); + if (!dealloc_decl) + return false; + + if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl) + && !DECL_IS_REPLACEABLE_OPERATOR (dealloc_decl)) + { + /* A call to a user-defined operator delete with a pointer plus offset + may be valid if it's returned from an unknown function (i.e., one + that's not operator new). */ + if (TREE_CODE (aref.ref) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); + if (is_gimple_call (def_stmt)) + { + tree alloc_decl = gimple_call_fndecl (def_stmt); + if (!alloc_decl || !DECL_IS_OPERATOR_NEW_P (alloc_decl)) + return false; + } + } + } + + char offstr[80]; + offstr[0] = '\0'; + if (wi::fits_shwi_p (aref.offrng[0])) + { + if (aref.offrng[0] == aref.offrng[1] + || !wi::fits_shwi_p (aref.offrng[1])) + sprintf (offstr, " %lli", + (long long)aref.offrng[0].to_shwi ()); + else + sprintf (offstr, " [%lli, %lli]", + (long long)aref.offrng[0].to_shwi (), + (long long)aref.offrng[1].to_shwi ()); + } + + if (!warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on pointer %qE with nonzero offset%s", + dealloc_decl, aref.ref, offstr)) + return false; + + if (DECL_P (aref.ref)) + inform (DECL_SOURCE_LOCATION (aref.ref), "declared here"); + else if (TREE_CODE (aref.ref) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); + if (is_gimple_call (def_stmt)) + { + location_t def_loc = gimple_location (def_stmt); + tree alloc_decl = gimple_call_fndecl (def_stmt); + if (alloc_decl) + inform (def_loc, + "returned from %qD", alloc_decl); + else if (tree alloc_fntype = gimple_call_fntype (def_stmt)) + inform (def_loc, + "returned from %qT", alloc_fntype); + else + inform (def_loc, "obtained here"); + } + } + + return true; +} + +/* Issue a warning if a deallocation function such as free, realloc, + or C++ operator delete is called with an argument not returned by + a matching allocation function such as malloc or the corresponding + form of C++ operatorn new. */ + +void +maybe_emit_free_warning (gcall *call) +{ + tree fndecl = gimple_call_fndecl (call); + if (!fndecl) + return; + + unsigned argno = fndecl_dealloc_argno (fndecl); + if ((unsigned) gimple_call_num_args (call) <= argno) + return; + + tree ptr = gimple_call_arg (call, argno); + if (integer_zerop (ptr)) + return; + + access_ref aref; + if (!compute_objsize (ptr, 0, &aref)) + return; + + tree ref = aref.ref; + if (integer_zerop (ref)) + return; + + tree dealloc_decl = fndecl; + location_t loc = gimple_location (call); + + if (DECL_P (ref) || EXPR_P (ref)) + { + /* Diagnose freeing a declared object. */ + if (aref.ref_declared () + && warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on unallocated object %qD", + dealloc_decl, ref)) + { + loc = (DECL_P (ref) + ? DECL_SOURCE_LOCATION (ref) + : EXPR_LOCATION (ref)); + inform (loc, "declared here"); + return; + } + + /* Diagnose freeing a pointer that includes a positive offset. + Such a pointer cannot refer to the beginning of an allocated + object. A negative offset may refer to it. */ + if (aref.sizrng[0] != aref.sizrng[1] + && warn_dealloc_offset (loc, call, aref)) + return; + } + else if (CONSTANT_CLASS_P (ref)) + { + if (warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on a pointer to an unallocated " + "object %qE", dealloc_decl, ref)) + { + if (TREE_CODE (ptr) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_assign (def_stmt)) + { + location_t loc = gimple_location (def_stmt); + inform (loc, "assigned here"); + } + } + return; + } + } + else if (TREE_CODE (ref) == SSA_NAME) + { + /* Also warn if the pointer argument refers to the result + of an allocation call like alloca or VLA. */ + gimple *def_stmt = SSA_NAME_DEF_STMT (ref); + if (is_gimple_call (def_stmt)) + { + bool warned = false; + if (gimple_call_alloc_p (def_stmt)) + { + if (matching_alloc_calls_p (def_stmt, dealloc_decl)) + { + if (warn_dealloc_offset (loc, call, aref)) + return; + } + else + { + tree alloc_decl = gimple_call_fndecl (def_stmt); + const opt_code opt = + (DECL_IS_OPERATOR_NEW_P (alloc_decl) + || DECL_IS_OPERATOR_DELETE_P (dealloc_decl) + ? OPT_Wmismatched_new_delete + : OPT_Wmismatched_dealloc); + warned = warning_at (loc, opt, + "%qD called on pointer returned " + "from a mismatched allocation " + "function", dealloc_decl); + } + } + else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA) + || gimple_call_builtin_p (def_stmt, + BUILT_IN_ALLOCA_WITH_ALIGN)) + warned = warning_at (loc, OPT_Wfree_nonheap_object, + "%qD called on pointer to " + "an unallocated object", + dealloc_decl); + else if (warn_dealloc_offset (loc, call, aref)) + return; + + if (warned) + { + tree fndecl = gimple_call_fndecl (def_stmt); + inform (gimple_location (def_stmt), + "returned from %qD", fndecl); + return; + } + } + else if (gimple_nop_p (def_stmt)) + { + ref = SSA_NAME_VAR (ref); + /* Diagnose freeing a pointer that includes a positive offset. */ + if (TREE_CODE (ref) == PARM_DECL + && !aref.deref + && aref.sizrng[0] != aref.sizrng[1] + && aref.offrng[0] > 0 && aref.offrng[1] > 0 + && warn_dealloc_offset (loc, call, aref)) + return; + } + } +} + +namespace { + +const pass_data pass_data_waccess = { + GIMPLE_PASS, + "waccess", + OPTGROUP_NONE, + TV_NONE, + PROP_cfg, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* properties_start */ + 0, /* properties_finish */ +}; + +/* Pass to detect invalid accesses. */ +class pass_waccess : public gimple_opt_pass +{ + public: + pass_waccess (gcc::context *ctxt) + : gimple_opt_pass (pass_data_waccess, ctxt), m_ranger () + { } + + opt_pass *clone () { return new pass_waccess (m_ctxt); } + + virtual bool gate (function *); + virtual unsigned int execute (function *); + + void check (basic_block); + void check (gcall *); + +private: + gimple_ranger *m_ranger; +}; + +/* Return true when any checks performed by the pass are enabled. */ + +bool +pass_waccess::gate (function *) +{ + return (warn_free_nonheap_object + || warn_mismatched_alloc + || warn_mismatched_new_delete); +} + +/* Check call STMT for invalid accesses. */ + +void +pass_waccess::check (gcall *stmt) +{ + maybe_emit_free_warning (stmt); +} + +/* Check basic block BB for invalid accesses. */ + +void +pass_waccess::check (basic_block bb) +{ + /* Iterate over statements, looking for function calls. */ + for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si)) + { + if (gcall *call = dyn_cast (gsi_stmt (si))) + check (call); + } +} + +/* Check function FUN for invalid accesses. */ + +unsigned +pass_waccess::execute (function *fun) +{ + basic_block bb; + FOR_EACH_BB_FN (bb, fun) + check (bb); + + return 0; +} + +} // namespace + +/* Return a new instance of the pass. */ + +gimple_opt_pass * +make_pass_warn_access (gcc::context *ctxt) +{ + return new pass_waccess (ctxt); +} diff --git a/gcc/gimple-ssa-warn-access.h b/gcc/gimple-ssa-warn-access.h new file mode 100644 index 000000000000..6197574cf449 --- /dev/null +++ b/gcc/gimple-ssa-warn-access.h @@ -0,0 +1,37 @@ +/* Pass to detect and issue warnings for invalid accesses, including + invalid or mismatched allocation/deallocation calls. + + Copyright (C) 2020-2021 Free Software Foundation, Inc. + Contributed by Martin Sebor . + + 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 + . */ + +#ifndef GCC_GIMPLE_SSA_WARN_ACCESS_H +#define GCC_GIMPLE_SSA_WARN_ACCESS_H + +extern bool check_nul_terminated_array (tree, tree, tree = NULL_TREE); +extern void warn_string_no_nul (location_t, tree, const char *, tree, + tree, tree = NULL_TREE, bool = false, + const wide_int[2] = NULL); +extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); +extern void get_size_range (tree, tree[2], const offset_int[2]); + +class access_data; +extern bool maybe_warn_for_bound (opt_code, location_t, tree, tree, + tree[2], tree, const access_data * = NULL); + +#endif // GCC_GIMPLE_SSA_WARN_ACCESS_H diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c index efb8db98393a..404acb03195b 100644 --- a/gcc/gimple-ssa-warn-restrict.c +++ b/gcc/gimple-ssa-warn-restrict.c @@ -26,7 +26,7 @@ #include "tree.h" #include "gimple.h" #include "tree-pass.h" -#include "builtins.h" +#include "pointer-query.h" #include "ssa.h" #include "gimple-pretty-print.h" #include "gimple-ssa-warn-restrict.h" diff --git a/gcc/passes.def b/gcc/passes.def index f5d88a61b0e8..e2858368b7dd 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -419,6 +419,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_gimple_isel); NEXT_PASS (pass_cleanup_cfg_post_optimizing); NEXT_PASS (pass_warn_function_noreturn); + NEXT_PASS (pass_warn_access); NEXT_PASS (pass_expand); diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc new file mode 100644 index 000000000000..bc7cac092f55 --- /dev/null +++ b/gcc/pointer-query.cc @@ -0,0 +1,1895 @@ +/* Definitions of the pointer_query and related classes. + + Copyright (C) 2020-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 + . */ + +#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 "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 "gimple-range.h" + +#include "pointer-query.h" + +static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &, + pointer_query *); + +/* Wrapper around the wide_int overload of get_range that accepts + offset_int instead. For middle end expressions returns the same + result. For a subset of nonconstamt expressions emitted by the front + end determines a more precise range than would be possible otherwise. */ + +static bool +get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals) +{ + offset_int add = 0; + if (TREE_CODE (x) == PLUS_EXPR) + { + /* Handle constant offsets in pointer addition expressions seen + n the front end IL. */ + tree op = TREE_OPERAND (x, 1); + if (TREE_CODE (op) == INTEGER_CST) + { + op = fold_convert (signed_type_for (TREE_TYPE (op)), op); + add = wi::to_offset (op); + x = TREE_OPERAND (x, 0); + } + } + + if (TREE_CODE (x) == NOP_EXPR) + /* Also handle conversions to sizetype seen in the front end IL. */ + x = TREE_OPERAND (x, 0); + + tree type = TREE_TYPE (x); + if (!INTEGRAL_TYPE_P (type) && !POINTER_TYPE_P (type)) + return false; + + if (TREE_CODE (x) != INTEGER_CST + && TREE_CODE (x) != SSA_NAME) + { + if (TYPE_UNSIGNED (type) + && TYPE_PRECISION (type) == TYPE_PRECISION (sizetype)) + type = signed_type_for (type); + + r[0] = wi::to_offset (TYPE_MIN_VALUE (type)) + add; + r[1] = wi::to_offset (TYPE_MAX_VALUE (type)) + add; + return x; + } + + wide_int wr[2]; + if (!get_range (x, stmt, wr, rvals)) + return false; + + signop sgn = SIGNED; + /* Only convert signed integers or unsigned sizetype to a signed + offset and avoid converting large positive values in narrower + types to negative offsets. */ + if (TYPE_UNSIGNED (type) + && wr[0].get_precision () < TYPE_PRECISION (sizetype)) + sgn = UNSIGNED; + + r[0] = offset_int::from (wr[0], sgn); + r[1] = offset_int::from (wr[1], sgn); + return true; +} + +/* Return the argument that the call STMT to a built-in function returns + or null if it doesn't. On success, set OFFRNG[] to the range of offsets + from the argument reflected in the value returned by the built-in if it + can be determined, otherwise to 0 and HWI_M1U respectively. Set + *PAST_END for functions like mempcpy that might return a past the end + pointer (most functions return a dereferenceable pointer to an existing + element of an array). */ + +static tree +gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end, + range_query *rvals) +{ + /* Clear and set below for the rare function(s) that might return + a past-the-end pointer. */ + *past_end = false; + + { + /* Check for attribute fn spec to see if the function returns one + of its arguments. */ + attr_fnspec fnspec = gimple_call_fnspec (as_a (stmt)); + unsigned int argno; + if (fnspec.returns_arg (&argno)) + { + /* Functions return the first argument (not a range). */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, argno); + } + } + + if (gimple_call_num_args (stmt) < 1) + return NULL_TREE; + + tree fn = gimple_call_fndecl (stmt); + if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + /* See if this is a call to placement new. */ + if (!fn + || !DECL_IS_OPERATOR_NEW_P (fn) + || DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fn)) + return NULL_TREE; + + /* Check the mangling, keeping in mind that operator new takes + a size_t which could be unsigned int or unsigned long. */ + tree fname = DECL_ASSEMBLER_NAME (fn); + if (!id_equal (fname, "_ZnwjPv") // ordinary form + && !id_equal (fname, "_ZnwmPv") // ordinary form + && !id_equal (fname, "_ZnajPv") // array form + && !id_equal (fname, "_ZnamPv")) // array form + return NULL_TREE; + + if (gimple_call_num_args (stmt) != 2) + return NULL_TREE; + + /* Allocation functions return a pointer to the beginning. */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, 1); + } + + switch (DECL_FUNCTION_CODE (fn)) + { + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMSET: + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + /* Functions return the first argument (not a range). */ + offrng[0] = offrng[1] = 0; + return gimple_call_arg (stmt, 0); + + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + { + /* The returned pointer is in a range constrained by the smaller + of the upper bound of the size argument and the source object + size. */ + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + tree off = gimple_call_arg (stmt, 2); + bool off_valid = get_offset_range (off, stmt, offrng, rvals); + if (!off_valid || offrng[0] != offrng[1]) + { + /* If the offset is either indeterminate or in some range, + try to constrain its upper bound to at most the size + of the source object. */ + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals) + && aref.sizrng[1] < offrng[1]) + offrng[1] = aref.sizrng[1]; + } + + /* Mempcpy may return a past-the-end pointer. */ + *past_end = true; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_MEMCHR: + { + tree off = gimple_call_arg (stmt, 2); + if (get_offset_range (off, stmt, offrng, rvals)) + offrng[1] -= 1; + else + offrng[1] = HOST_WIDE_INT_M1U; + + offrng[0] = 0; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_STRCHR: + case BUILT_IN_STRRCHR: + case BUILT_IN_STRSTR: + offrng[0] = 0; + offrng[1] = HOST_WIDE_INT_M1U; + return gimple_call_arg (stmt, 0); + + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + { + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals)) + offrng[1] = aref.sizrng[1] - 1; + else + offrng[1] = HOST_WIDE_INT_M1U; + + offrng[0] = 0; + return gimple_call_arg (stmt, 0); + } + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + { + /* The returned pointer is in a range between the first argument + and it plus the smaller of the upper bound of the size argument + and the source object size. */ + offrng[1] = HOST_WIDE_INT_M1U; + tree off = gimple_call_arg (stmt, 2); + if (!get_offset_range (off, stmt, offrng, rvals) + || offrng[0] != offrng[1]) + { + /* If the offset is either indeterminate or in some range, + try to constrain its upper bound to at most the size + of the source object. */ + access_ref aref; + tree src = gimple_call_arg (stmt, 1); + if (compute_objsize (src, 1, &aref, rvals) + && aref.sizrng[1] < offrng[1]) + offrng[1] = aref.sizrng[1]; + } + + /* When the source is the empty string the returned pointer is + a copy of the argument. Otherwise stpcpy can also return + a past-the-end pointer. */ + offrng[0] = 0; + *past_end = true; + return gimple_call_arg (stmt, 0); + } + + default: + break; + } + + return NULL_TREE; +} + +/* If STMT is a call to an allocation function, returns the constant + maximum size of the object allocated by the call represented as + sizetype. If nonnull, sets RNG1[] to the range of the size. + When nonnull, uses RVALS for range information, otherwise gets global + range info. + Returns null when STMT is not a call to a valid allocation function. */ + +tree +gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, + range_query * /* = NULL */) +{ + if (!stmt || !is_gimple_call (stmt)) + return NULL_TREE; + + tree allocfntype; + if (tree fndecl = gimple_call_fndecl (stmt)) + allocfntype = TREE_TYPE (fndecl); + else + allocfntype = gimple_call_fntype (stmt); + + if (!allocfntype) + return NULL_TREE; + + unsigned argidx1 = UINT_MAX, argidx2 = UINT_MAX; + tree at = lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (allocfntype)); + if (!at) + { + if (!gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + return NULL_TREE; + + argidx1 = 0; + } + + unsigned nargs = gimple_call_num_args (stmt); + + if (argidx1 == UINT_MAX) + { + tree atval = TREE_VALUE (at); + if (!atval) + return NULL_TREE; + + argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; + if (nargs <= argidx1) + return NULL_TREE; + + atval = TREE_CHAIN (atval); + if (atval) + { + argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval)) - 1; + if (nargs <= argidx2) + return NULL_TREE; + } + } + + tree size = gimple_call_arg (stmt, argidx1); + + wide_int rng1_buf[2]; + /* If RNG1 is not set, use the buffer. */ + if (!rng1) + rng1 = rng1_buf; + + /* Use maximum precision to avoid overflow below. */ + const int prec = ADDR_MAX_PRECISION; + + { + tree r[2]; + /* Determine the largest valid range size, including zero. */ + if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng1[0] = wi::to_wide (r[0], prec); + rng1[1] = wi::to_wide (r[1], prec); + } + + if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) + return fold_convert (sizetype, size); + + /* To handle ranges do the math in wide_int and return the product + of the upper bounds as a constant. Ignore anti-ranges. */ + tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node; + wide_int rng2[2]; + { + tree r[2]; + /* As above, use the full non-negative range on failure. */ + if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST)) + return NULL_TREE; + rng2[0] = wi::to_wide (r[0], prec); + rng2[1] = wi::to_wide (r[1], prec); + } + + /* Compute products of both bounds for the caller but return the lesser + of SIZE_MAX and the product of the upper bounds as a constant. */ + rng1[0] = rng1[0] * rng2[0]; + rng1[1] = rng1[1] * rng2[1]; + + const tree size_max = TYPE_MAX_VALUE (sizetype); + if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec))) + { + rng1[1] = wi::to_wide (size_max, prec); + return size_max; + } + + return wide_int_to_tree (sizetype, rng1[1]); +} + +/* For an access to an object referenced to by the function parameter PTR + of pointer type, and set RNG[] to the range of sizes of the object + obtainedfrom the attribute access specification for the current function. + Set STATIC_ARRAY if the array parameter has been declared [static]. + Return the function parameter on success and null otherwise. */ + +tree +gimple_parm_array_size (tree ptr, wide_int rng[2], + bool *static_array /* = NULL */) +{ + /* For a function argument try to determine the byte size of the array + from the current function declaratation (e.g., attribute access or + related). */ + tree var = SSA_NAME_VAR (ptr); + if (TREE_CODE (var) != PARM_DECL) + return NULL_TREE; + + const unsigned prec = TYPE_PRECISION (sizetype); + + rdwr_map rdwr_idx; + attr_access *access = get_parm_access (rdwr_idx, var); + if (!access) + return NULL_TREE; + + if (access->sizarg != UINT_MAX) + { + /* TODO: Try to extract the range from the argument based on + those of subsequent assertions or based on known calls to + the current function. */ + return NULL_TREE; + } + + if (!access->minsize) + return NULL_TREE; + + /* Only consider ordinary array bound at level 2 (or above if it's + ever added). */ + if (warn_array_parameter < 2 && !access->static_p) + return NULL_TREE; + + if (static_array) + *static_array = access->static_p; + + rng[0] = wi::zero (prec); + rng[1] = wi::uhwi (access->minsize, prec); + /* Multiply the array bound encoded in the attribute by the size + of what the pointer argument to which it decays points to. */ + tree eltype = TREE_TYPE (TREE_TYPE (ptr)); + tree size = TYPE_SIZE_UNIT (eltype); + if (!size || TREE_CODE (size) != INTEGER_CST) + return NULL_TREE; + + rng[1] *= wi::to_wide (size, prec); + return var; +} + +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; + offmax[0] = offmax[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 (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 *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; +} + +/* Return true if the offset and object size are in range for SIZE. */ + +bool +access_ref::offset_in_range (const offset_int &size) const +{ + if (size_remaining () < size) + return false; + + if (base0) + return offmax[0] >= 0 && offmax[1] <= sizrng[1]; + + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + return offmax[0] > -maxoff && offmax[1] < maxoff; +} + +/* 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; + if (offmax[0] > 0) + offmax[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; + } + + /* Set the minimum and maximmum computed so far. */ + if (offrng[1] < 0 && offrng[1] < offmax[0]) + offmax[0] = offrng[1]; + if (offrng[0] > 0 && offrng[0] > offmax[1]) + offmax[1] = offrng[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]; + } +} + +/* Issue one inform message describing each target of an access REF. + WRITE is set for a write access and clear for a read access. */ + +void +access_ref::inform_access (access_mode mode) const +{ + const access_ref &aref = *this; + if (!aref.ref) + return; + + if (aref.phi ()) + { + /* Set MAXREF to refer to the largest object and fill ALL_REFS + with data for all objects referenced by the PHI arguments. */ + access_ref maxref; + auto_vec all_refs; + if (!get_ref (&all_refs, &maxref)) + return; + + /* Except for MAXREF, the rest of the arguments' offsets need not + reflect one added to the PHI itself. Determine the latter from + MAXREF on which the result is based. */ + const offset_int orng[] = + { + offrng[0] - maxref.offrng[0], + wi::smax (offrng[1] - maxref.offrng[1], offrng[0]), + }; + + /* Add the final PHI's offset to that of each of the arguments + and recurse to issue an inform message for it. */ + for (unsigned i = 0; i != all_refs.length (); ++i) + { + /* Skip any PHIs; those could lead to infinite recursion. */ + if (all_refs[i].phi ()) + continue; + + all_refs[i].add_offset (orng[0], orng[1]); + all_refs[i].inform_access (mode); + } + return; + } + + /* Convert offset range and avoid including a zero range since it + isn't necessarily meaningful. */ + HOST_WIDE_INT diff_min = tree_to_shwi (TYPE_MIN_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT diff_max = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node)); + HOST_WIDE_INT minoff; + HOST_WIDE_INT maxoff = diff_max; + if (wi::fits_shwi_p (aref.offrng[0])) + minoff = aref.offrng[0].to_shwi (); + else + minoff = aref.offrng[0] < 0 ? diff_min : diff_max; + + if (wi::fits_shwi_p (aref.offrng[1])) + maxoff = aref.offrng[1].to_shwi (); + + if (maxoff <= diff_min || maxoff >= diff_max) + /* Avoid mentioning an upper bound that's equal to or in excess + of the maximum of ptrdiff_t. */ + maxoff = minoff; + + /* Convert size range and always include it since all sizes are + meaningful. */ + unsigned long long minsize = 0, maxsize = 0; + if (wi::fits_shwi_p (aref.sizrng[0]) + && wi::fits_shwi_p (aref.sizrng[1])) + { + minsize = aref.sizrng[0].to_shwi (); + maxsize = aref.sizrng[1].to_shwi (); + } + + /* SIZRNG doesn't necessarily have the same range as the allocation + size determined by gimple_call_alloc_size (). */ + char sizestr[80]; + if (minsize == maxsize) + sprintf (sizestr, "%llu", minsize); + else + sprintf (sizestr, "[%llu, %llu]", minsize, maxsize); + + char offstr[80]; + if (minoff == 0 + && (maxoff == 0 || aref.sizrng[1] <= maxoff)) + offstr[0] = '\0'; + else if (minoff == maxoff) + sprintf (offstr, "%lli", (long long) minoff); + else + sprintf (offstr, "[%lli, %lli]", (long long) minoff, (long long) maxoff); + + location_t loc = UNKNOWN_LOCATION; + + tree ref = this->ref; + tree allocfn = NULL_TREE; + if (TREE_CODE (ref) == SSA_NAME) + { + gimple *stmt = SSA_NAME_DEF_STMT (ref); + if (is_gimple_call (stmt)) + { + loc = gimple_location (stmt); + if (gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA_WITH_ALIGN)) + { + /* Strip the SSA_NAME suffix from the variable name and + recreate an identifier with the VLA's original name. */ + ref = gimple_call_lhs (stmt); + if (SSA_NAME_IDENTIFIER (ref)) + { + ref = SSA_NAME_IDENTIFIER (ref); + const char *id = IDENTIFIER_POINTER (ref); + size_t len = strcspn (id, ".$"); + if (!len) + len = strlen (id); + ref = get_identifier_with_length (id, len); + } + } + else + { + /* Except for VLAs, retrieve the allocation function. */ + allocfn = gimple_call_fndecl (stmt); + if (!allocfn) + allocfn = gimple_call_fn (stmt); + if (TREE_CODE (allocfn) == SSA_NAME) + { + /* For an ALLOC_CALL via a function pointer make a small + effort to determine the destination of the pointer. */ + gimple *def = SSA_NAME_DEF_STMT (allocfn); + if (gimple_assign_single_p (def)) + { + tree rhs = gimple_assign_rhs1 (def); + if (DECL_P (rhs)) + allocfn = rhs; + else if (TREE_CODE (rhs) == COMPONENT_REF) + allocfn = TREE_OPERAND (rhs, 1); + } + } + } + } + else if (gimple_nop_p (stmt)) + /* Handle DECL_PARM below. */ + ref = SSA_NAME_VAR (ref); + } + + if (DECL_P (ref)) + loc = DECL_SOURCE_LOCATION (ref); + else if (EXPR_P (ref) && EXPR_HAS_LOCATION (ref)) + loc = EXPR_LOCATION (ref); + else if (TREE_CODE (ref) != IDENTIFIER_NODE + && TREE_CODE (ref) != SSA_NAME) + return; + + if (mode == access_read_write || mode == access_write_only) + { + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into destination object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "destination object %qE of size %s", ref, sizestr); + return; + } + + if (*offstr) + inform (loc, + "at offset %s into destination object of size %s " + "allocated by %qE", offstr, sizestr, allocfn); + else + inform (loc, "destination object of size %s allocated by %qE", + sizestr, allocfn); + return; + } + + if (mode == access_read_only) + { + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into source object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "source object %qE of size %s", ref, sizestr); + + return; + } + + if (*offstr) + inform (loc, + "at offset %s into source object of size %s allocated by %qE", + offstr, sizestr, allocfn); + else + inform (loc, "source object of size %s allocated by %qE", + sizestr, allocfn); + return; + } + + if (allocfn == NULL_TREE) + { + if (*offstr) + inform (loc, "at offset %s into object %qE of size %s", + offstr, ref, sizestr); + else + inform (loc, "object %qE of size %s", ref, sizestr); + + return; + } + + if (*offstr) + inform (loc, + "at offset %s into object of size %s allocated by %qE", + offstr, sizestr, allocfn); + else + inform (loc, "object of size %s allocated by %qE", + sizestr, allocfn); +} + +/* 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 (); +} + +/* A helper of compute_objsize_r() to determine the size from an assignment + statement STMT with the RHS of either MIN_EXPR or MAX_EXPR. */ + +static bool +handle_min_max_size (gimple *stmt, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + tree_code code = gimple_assign_rhs_code (stmt); + + tree ptr = gimple_assign_rhs1 (stmt); + + /* In a valid MAX_/MIN_EXPR both operands must refer to the same array. + Determine the size/offset of each and use the one with more or less + space remaining, respectively. If either fails, use the information + determined from the other instead, adjusted up or down as appropriate + for the expression. */ + access_ref aref[2] = { *pref, *pref }; + if (!compute_objsize_r (ptr, ostype, &aref[0], snlim, qry)) + { + aref[0].base0 = false; + aref[0].offrng[0] = aref[0].offrng[1] = 0; + aref[0].add_max_offset (); + aref[0].set_max_size_range (); + } + + ptr = gimple_assign_rhs2 (stmt); + if (!compute_objsize_r (ptr, ostype, &aref[1], snlim, qry)) + { + aref[1].base0 = false; + aref[1].offrng[0] = aref[1].offrng[1] = 0; + aref[1].add_max_offset (); + aref[1].set_max_size_range (); + } + + if (!aref[0].ref && !aref[1].ref) + /* Fail if the identity of neither argument could be determined. */ + return false; + + bool i0 = false; + if (aref[0].ref && aref[0].base0) + { + if (aref[1].ref && aref[1].base0) + { + /* If the object referenced by both arguments has been determined + set *PREF to the one with more or less space remainng, whichever + is appopriate for CODE. + TODO: Indicate when the objects are distinct so it can be + diagnosed. */ + i0 = code == MAX_EXPR; + const bool i1 = !i0; + + if (aref[i0].size_remaining () < aref[i1].size_remaining ()) + *pref = aref[i1]; + else + *pref = aref[i0]; + return true; + } + + /* If only the object referenced by one of the arguments could be + determined, use it and... */ + *pref = aref[0]; + i0 = true; + } + else + *pref = aref[1]; + + const bool i1 = !i0; + /* ...see if the offset obtained from the other pointer can be used + to tighten up the bound on the offset obtained from the first. */ + if ((code == MAX_EXPR && aref[i1].offrng[1] < aref[i0].offrng[0]) + || (code == MIN_EXPR && aref[i0].offrng[0] < aref[i1].offrng[1])) + { + pref->offrng[0] = aref[i0].offrng[0]; + pref->offrng[1] = aref[i0].offrng[1]; + } + return true; +} + +/* A helper of compute_objsize_r() to determine the size from ARRAY_REF + AREF. ADDR is true if PTR is the operand of ADDR_EXPR. Return true + on success and false on failure. */ + +static bool +handle_array_ref (tree aref, bool addr, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + gcc_assert (TREE_CODE (aref) == ARRAY_REF); + + ++pref->deref; + + tree arefop = TREE_OPERAND (aref, 0); + tree reftype = TREE_TYPE (arefop); + if (!addr && TREE_CODE (TREE_TYPE (reftype)) == POINTER_TYPE) + /* Avoid arrays of pointers. FIXME: Hande pointers to arrays + of known bound. */ + return false; + + if (!compute_objsize_r (arefop, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (aref, 1)); + range_query *const rvals = qry ? qry->rvals : NULL; + if (!get_offset_range (off, NULL, orng, rvals)) + { + /* Set ORNG to the maximum offset representable in ptrdiff_t. */ + orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + orng[0] = -orng[1] - 1; + } + + /* Convert the array index range determined above to a byte + offset. */ + tree lowbnd = array_ref_low_bound (aref); + if (!integer_zerop (lowbnd) && tree_fits_uhwi_p (lowbnd)) + { + /* Adjust the index by the low bound of the array domain + (normally zero but 1 in Fortran). */ + unsigned HOST_WIDE_INT lb = tree_to_uhwi (lowbnd); + orng[0] -= lb; + orng[1] -= lb; + } + + tree eltype = TREE_TYPE (aref); + tree tpsize = TYPE_SIZE_UNIT (eltype); + if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST) + { + pref->add_max_offset (); + return true; + } + + offset_int sz = wi::to_offset (tpsize); + orng[0] *= sz; + orng[1] *= sz; + + if (ostype && TREE_CODE (eltype) == ARRAY_TYPE) + { + /* Except for the permissive raw memory functions which use + the size of the whole object determined above, use the size + of the referenced array. Because the overall offset is from + the beginning of the complete array object add this overall + offset to the size of array. */ + offset_int sizrng[2] = + { + pref->offrng[0] + orng[0] + sz, + pref->offrng[1] + orng[1] + sz + }; + if (sizrng[1] < sizrng[0]) + std::swap (sizrng[0], sizrng[1]); + if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0]) + pref->sizrng[0] = sizrng[0]; + if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1]) + pref->sizrng[1] = sizrng[1]; + } + + pref->add_offset (orng[0], orng[1]); + return true; +} + +/* A helper of compute_objsize_r() to determine the size from MEM_REF + MREF. Return true on success and false on failure. */ + +static bool +handle_mem_ref (tree mref, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + gcc_assert (TREE_CODE (mref) == MEM_REF); + + ++pref->deref; + + if (VECTOR_TYPE_P (TREE_TYPE (mref))) + { + /* Hack: Handle MEM_REFs of vector types as those to complete + objects; those may be synthesized from multiple assignments + to consecutive data members (see PR 93200 and 96963). + FIXME: Vectorized assignments should only be present after + vectorization so this hack is only necessary after it has + run and could be avoided in calls from prior passes (e.g., + tree-ssa-strlen.c). + FIXME: Deal with this more generally, e.g., by marking up + such MEM_REFs at the time they're created. */ + ostype = 0; + } + + tree mrefop = TREE_OPERAND (mref, 0); + if (!compute_objsize_r (mrefop, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (mref, 1)); + range_query *const rvals = qry ? qry->rvals : NULL; + if (!get_offset_range (off, NULL, orng, rvals)) + { + /* Set ORNG to the maximum offset representable in ptrdiff_t. */ + orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + orng[0] = -orng[1] - 1; + } + + pref->add_offset (orng[0], orng[1]); + return true; +} + +/* Helper to compute the size of the object referenced by the PTR + expression which must have pointer type, using Object Size type + OSTYPE (only the least significant 2 bits are used). + On success, sets PREF->REF to the DECL of the referenced object + if it's unique, otherwise to null, PREF->OFFRNG to the range of + offsets into it, and PREF->SIZRNG to the range of sizes of + the object(s). + SNLIM is used to avoid visiting the same PHI operand multiple + times, and, when nonnull, RVALS to determine range information. + Returns true on success, false when a meaningful size (or range) + cannot be determined. + + The function is intended for diagnostics and should not be used + to influence code generation or optimization. */ + +static bool +compute_objsize_r (tree ptr, int ostype, access_ref *pref, + ssa_name_limit_t &snlim, pointer_query *qry) +{ + STRIP_NOPS (ptr); + + const bool addr = TREE_CODE (ptr) == ADDR_EXPR; + if (addr) + { + --pref->deref; + ptr = TREE_OPERAND (ptr, 0); + } + + if (DECL_P (ptr)) + { + pref->ref = ptr; + + if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr))) + { + /* Set the maximum size if the reference is to the pointer + itself (as opposed to what it points to), and clear + BASE0 since the offset isn't necessarily zero-based. */ + pref->set_max_size_range (); + pref->base0 = false; + return true; + } + + if (tree size = decl_init_size (ptr, false)) + if (TREE_CODE (size) == INTEGER_CST) + { + pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); + return true; + } + + pref->set_max_size_range (); + return true; + } + + const tree_code code = TREE_CODE (ptr); + range_query *const rvals = qry ? qry->rvals : NULL; + + if (code == BIT_FIELD_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2))); + pref->add_offset (off / BITS_PER_UNIT); + return true; + } + + if (code == COMPONENT_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (TREE_CODE (TREE_TYPE (ref)) == UNION_TYPE) + /* In accesses through union types consider the entire unions + rather than just their members. */ + ostype = 0; + tree field = TREE_OPERAND (ptr, 1); + + if (ostype == 0) + { + /* In OSTYPE zero (for raw memory functions like memcpy), use + the maximum size instead if the identity of the enclosing + object cannot be determined. */ + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* Otherwise, use the size of the enclosing object and add + the offset of the member to the offset computed so far. */ + tree offset = byte_position (field); + if (TREE_CODE (offset) == INTEGER_CST) + pref->add_offset (wi::to_offset (offset)); + else + pref->add_max_offset (); + + if (!pref->ref) + /* REF may have been already set to an SSA_NAME earlier + to provide better context for diagnostics. In that case, + leave it unchanged. */ + pref->ref = ref; + return true; + } + + pref->ref = field; + + if (!addr && POINTER_TYPE_P (TREE_TYPE (field))) + { + /* Set maximum size if the reference is to the pointer member + itself (as opposed to what it points to). */ + pref->set_max_size_range (); + return true; + } + + /* SAM is set for array members that might need special treatment. */ + special_array_member sam; + tree size = component_ref_size (ptr, &sam); + if (sam == special_array_member::int_0) + pref->sizrng[0] = pref->sizrng[1] = 0; + else if (!pref->trail1special && sam == special_array_member::trail_1) + pref->sizrng[0] = pref->sizrng[1] = 1; + else if (size && TREE_CODE (size) == INTEGER_CST) + pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size); + else + { + /* When the size of the member is unknown it's either a flexible + array member or a trailing special array member (either zero + length or one-element). Set the size to the maximum minus + the constant size of the type. */ + pref->sizrng[0] = 0; + pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref))) + if (TREE_CODE (recsize) == INTEGER_CST) + pref->sizrng[1] -= wi::to_offset (recsize); + } + return true; + } + + if (code == ARRAY_REF) + return handle_array_ref (ptr, addr, ostype, pref, snlim, qry); + + if (code == MEM_REF) + return handle_mem_ref (ptr, ostype, pref, snlim, qry); + + if (code == TARGET_MEM_REF) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* TODO: Handle remaining operands. Until then, add maximum offset. */ + pref->ref = ptr; + pref->add_max_offset (); + return true; + } + + if (code == INTEGER_CST) + { + /* Pointer constants other than null are most likely the result + of erroneous null pointer addition/subtraction. Set size to + zero. For null pointers, set size to the maximum for now + since those may be the result of jump threading. */ + if (integer_zerop (ptr)) + pref->set_max_size_range (); + else + pref->sizrng[0] = pref->sizrng[1] = 0; + pref->ref = ptr; + + return true; + } + + if (code == STRING_CST) + { + pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr); + pref->ref = ptr; + return true; + } + + if (code == POINTER_PLUS_EXPR) + { + tree ref = TREE_OPERAND (ptr, 0); + if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) + return false; + + /* Clear DEREF since the offset is being applied to the target + of the dereference. */ + pref->deref = 0; + + offset_int orng[2]; + tree off = pref->eval (TREE_OPERAND (ptr, 1)); + if (get_offset_range (off, NULL, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + return true; + } + + if (code == VIEW_CONVERT_EXPR) + { + ptr = TREE_OPERAND (ptr, 0); + return compute_objsize_r (ptr, ostype, pref, snlim, qry); + } + + if (code == SSA_NAME) + { + if (!snlim.next ()) + return false; + + /* Only process an SSA_NAME if the recursion limit has not yet + been reached. */ + if (qry) + { + if (++qry->depth) + qry->max_depth = qry->depth; + if (const access_ref *cache_ref = qry->get_ref (ptr)) + { + /* If the pointer is in the cache set *PREF to what it refers + to and return success. */ + *pref = *cache_ref; + return true; + } + } + + gimple *stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_call (stmt)) + { + /* If STMT is a call to an allocation function get the size + from its argument(s). If successful, also set *PREF->REF + to PTR for the caller to include in diagnostics. */ + wide_int wr[2]; + if (gimple_call_alloc_size (stmt, wr, rvals)) + { + pref->ref = ptr; + pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); + pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + /* Constrain both bounds to a valid size. */ + offset_int maxsize = wi::to_offset (max_object_size ()); + if (pref->sizrng[0] > maxsize) + pref->sizrng[0] = maxsize; + if (pref->sizrng[1] > maxsize) + pref->sizrng[1] = maxsize; + } + else + { + /* For functions known to return one of their pointer arguments + try to determine what the returned pointer points to, and on + success add OFFRNG which was set to the offset added by + the function (e.g., memchr) to the overall offset. */ + bool past_end; + offset_int offrng[2]; + if (tree ret = gimple_call_return_array (stmt, offrng, + &past_end, rvals)) + { + if (!compute_objsize_r (ret, ostype, pref, snlim, qry)) + return false; + + /* Cap OFFRNG[1] to at most the remaining size of + the object. */ + offset_int remrng[2]; + remrng[1] = pref->size_remaining (remrng); + if (remrng[1] != 0 && !past_end) + /* Decrement the size for functions that never return + a past-the-end pointer. */ + remrng[1] -= 1; + + if (remrng[1] < offrng[1]) + offrng[1] = remrng[1]; + pref->add_offset (offrng[0], offrng[1]); + } + else + { + /* For other calls that might return arbitrary pointers + including into the middle of objects set the size + range to maximum, clear PREF->BASE0, and also set + PREF->REF to include in diagnostics. */ + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + } + } + qry->put_ref (ptr, *pref); + return true; + } + + if (gimple_nop_p (stmt)) + { + /* For a function argument try to determine the byte size + of the array from the current function declaratation + (e.g., attribute access or related). */ + wide_int wr[2]; + bool static_array = false; + if (tree ref = gimple_parm_array_size (ptr, wr, &static_array)) + { + pref->parmarray = !static_array; + pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED); + pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED); + pref->ref = ref; + qry->put_ref (ptr, *pref); + return true; + } + + pref->set_max_size_range (); + pref->base0 = false; + pref->ref = ptr; + qry->put_ref (ptr, *pref); + return true; + } + + if (gimple_code (stmt) == GIMPLE_PHI) + { + pref->ref = ptr; + access_ref phi_ref = *pref; + if (!pref->get_ref (NULL, &phi_ref, ostype, &snlim, qry)) + return false; + *pref = phi_ref; + pref->ref = ptr; + qry->put_ref (ptr, *pref); + return true; + } + + if (!is_gimple_assign (stmt)) + { + /* Clear BASE0 since the assigned pointer might point into + the middle of the object, set the maximum size range and, + if the SSA_NAME refers to a function argumnent, set + PREF->REF to it. */ + pref->base0 = false; + pref->set_max_size_range (); + pref->ref = ptr; + return true; + } + + tree_code code = gimple_assign_rhs_code (stmt); + + if (code == MAX_EXPR || code == MIN_EXPR) + { + if (!handle_min_max_size (stmt, ostype, pref, snlim, qry)) + return false; + qry->put_ref (ptr, *pref); + return true; + } + + tree rhs = gimple_assign_rhs1 (stmt); + + if (code == ASSERT_EXPR) + { + rhs = TREE_OPERAND (rhs, 0); + return compute_objsize_r (rhs, ostype, pref, snlim, qry); + } + + if (code == POINTER_PLUS_EXPR + && TREE_CODE (TREE_TYPE (rhs)) == POINTER_TYPE) + { + /* Compute the size of the object first. */ + if (!compute_objsize_r (rhs, ostype, pref, snlim, qry)) + return false; + + offset_int orng[2]; + tree off = gimple_assign_rhs2 (stmt); + if (get_offset_range (off, stmt, orng, rvals)) + pref->add_offset (orng[0], orng[1]); + else + pref->add_max_offset (); + qry->put_ref (ptr, *pref); + return true; + } + + if (code == ADDR_EXPR + || code == SSA_NAME) + return compute_objsize_r (rhs, ostype, pref, snlim, qry); + + /* (This could also be an assignment from a nonlocal pointer.) Save + PTR to mention in diagnostics but otherwise treat it as a pointer + to an unknown object. */ + pref->ref = rhs; + pref->base0 = false; + pref->set_max_size_range (); + return true; + } + + /* Assume all other expressions point into an unknown object + of the maximum valid size. */ + pref->ref = ptr; + pref->base0 = false; + pref->set_max_size_range (); + if (TREE_CODE (ptr) == SSA_NAME) + qry->put_ref (ptr, *pref); + return true; +} + +/* A "public" wrapper around the above. Clients should use this overload + instead. */ + +tree +compute_objsize (tree ptr, int ostype, access_ref *pref, + range_query *rvals /* = NULL */) +{ + pointer_query qry; + qry.rvals = rvals; + ssa_name_limit_t snlim; + if (!compute_objsize_r (ptr, ostype, pref, snlim, &qry)) + return NULL_TREE; + + offset_int maxsize = pref->size_remaining (); + if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) + pref->offrng[0] = 0; + return wide_int_to_tree (sizetype, maxsize); +} + +/* Transitional wrapper. The function should be removed once callers + transition to the pointer_query API. */ + +tree +compute_objsize (tree ptr, int ostype, access_ref *pref, pointer_query *ptr_qry) +{ + pointer_query qry; + if (ptr_qry) + ptr_qry->depth = 0; + else + ptr_qry = &qry; + + ssa_name_limit_t snlim; + if (!compute_objsize_r (ptr, ostype, pref, snlim, ptr_qry)) + return NULL_TREE; + + offset_int maxsize = pref->size_remaining (); + if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0) + pref->offrng[0] = 0; + return wide_int_to_tree (sizetype, maxsize); +} + +/* Legacy wrapper around the above. The function should be removed + once callers transition to one of the two above. */ + +tree +compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */, + tree *poff /* = NULL */, range_query *rvals /* = NULL */) +{ + /* Set the initial offsets to zero and size to negative to indicate + none has been computed yet. */ + access_ref ref; + tree size = compute_objsize (ptr, ostype, &ref, rvals); + if (!size || !ref.base0) + return NULL_TREE; + + if (pdecl) + *pdecl = ref.ref; + + if (poff) + *poff = wide_int_to_tree (ptrdiff_type_node, ref.offrng[ref.offrng[0] < 0]); + + return size; +} diff --git a/gcc/pointer-query.h b/gcc/pointer-query.h new file mode 100644 index 000000000000..6168c809ccc9 --- /dev/null +++ b/gcc/pointer-query.h @@ -0,0 +1,234 @@ +/* Definitions of the pointer_query and related classes. + + Copyright (C) 2020-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 + . */ + +#ifndef GCC_POINTER_QUERY_H +#define GCC_POINTER_QUERY_H + +/* Describes recursion limits used by functions that follow use-def + chains of SSA_NAMEs. */ + +class ssa_name_limit_t +{ + bitmap visited; /* Bitmap of visited SSA_NAMEs. */ + unsigned ssa_def_max; /* Longest chain of SSA_NAMEs to follow. */ + + /* Not copyable or assignable. */ + DISABLE_COPY_AND_ASSIGN (ssa_name_limit_t); + +public: + + ssa_name_limit_t () + : visited (), + ssa_def_max (param_ssa_name_def_chain_limit) { } + + /* Set a bit for the PHI in VISITED and return true if it wasn't + already set. */ + bool visit_phi (tree); + /* Clear a bit for the PHI in VISITED. */ + void leave_phi (tree); + /* Return false if the SSA_NAME chain length counter has reached + the limit, otherwise increment the counter and return true. */ + bool next (); + + /* 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 next_phi (tree); + + ~ssa_name_limit_t (); +}; + +class pointer_query; + +/* Describes a reference to an object used in an access. */ +struct access_ref +{ + /* Set the bounds of the reference to at most as many bytes + as the first argument or unknown when null, and at least + one when the second argument is true unless the first one + is a constant zero. */ + access_ref (tree = NULL_TREE, bool = false); + + /* Return the PHI node REF refers to or null if it doesn't. */ + gphi *phi () const; + + /* Return the object to which REF refers. */ + tree get_ref (vec *, access_ref * = NULL, int = 1, + ssa_name_limit_t * = NULL, pointer_query * = NULL) const; + + /* Return true if OFFRNG is the constant zero. */ + bool offset_zero () const + { + return offrng[0] == 0 && offrng[1] == 0; + } + + /* Return true if OFFRNG is bounded to a subrange of offset values + valid for the largest possible object. */ + bool offset_bounded () const; + + /* Return the maximum amount of space remaining and if non-null, set + argument to the minimum. */ + offset_int size_remaining (offset_int * = NULL) const; + +/* Return true if the offset and object size are in range for SIZE. */ + bool offset_in_range (const offset_int &) const; + + /* Return true if *THIS is an access to a declared object. */ + bool ref_declared () const + { + return DECL_P (ref) && base0 && deref < 1; + } + + /* Set the size range to the maximum. */ + void set_max_size_range () + { + sizrng[0] = 0; + sizrng[1] = wi::to_offset (max_object_size ()); + } + + /* Add OFF to the offset range. */ + void add_offset (const offset_int &off) + { + add_offset (off, off); + } + + /* Add the range [MIN, MAX] to the offset range. */ + void add_offset (const offset_int &, const offset_int &); + + /* Add the maximum representable offset to the offset range. */ + void add_max_offset () + { + offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node)); + add_offset (-maxoff - 1, maxoff); + } + + /* Issue an informational message describing the target of an access + with the given mode. */ + void inform_access (access_mode) const; + + /* Reference to the accessed object(s). */ + tree ref; + + /* Range of byte offsets into and sizes of the object(s). */ + offset_int offrng[2]; + offset_int sizrng[2]; + /* The minimum and maximum offset computed. */ + offset_int offmax[2]; + /* Range of the bound of the access: denotes that the access + is at least BNDRNG[0] bytes but no more than BNDRNG[1]. + For string functions the size of the actual access is + further constrained by the length of the string. */ + offset_int bndrng[2]; + + /* Used to fold integer expressions when called from front ends. */ + tree (*eval)(tree); + /* Positive when REF is dereferenced, negative when its address is + taken. */ + int deref; + /* Set if trailing one-element arrays should be treated as flexible + array members. */ + bool trail1special; + /* Set if valid offsets must start at zero (for declared and allocated + objects but not for others referenced by pointers). */ + bool base0; + /* Set if REF refers to a function array parameter not declared + static. */ + bool parmarray; +}; + +class range_query; + +/* Queries and caches compute_objsize results. */ +class pointer_query +{ + DISABLE_COPY_AND_ASSIGN (pointer_query); + +public: + /* Type of the two-level cache object defined by clients of the class + to have pointer SSA_NAMEs cached for speedy access. */ + struct cache_type + { + /* 1-based indices into cache. */ + vec indices; + /* The cache itself. */ + vec access_refs; + }; + + /* Construct an object with the given Ranger instance and cache. */ + explicit pointer_query (range_query * = NULL, cache_type * = NULL); + + /* Retrieve the access_ref for a variable from cache if it's there. */ + const access_ref* get_ref (tree, int = 1) const; + + /* Retrieve the access_ref for a variable from cache or compute it. */ + bool get_ref (tree, access_ref*, int = 1); + + /* Add an access_ref for the SSA_NAME to the cache. */ + void put_ref (tree, const access_ref&, int = 1); + + /* Flush the cache. */ + void flush_cache (); + + /* A Ranger instance. May be null to use global ranges. */ + range_query *rvals; + /* Cache of SSA_NAMEs. May be null to disable caching. */ + cache_type *var_cache; + + /* Cache performance counters. */ + mutable unsigned hits; + mutable unsigned misses; + mutable unsigned failures; + mutable unsigned depth; + mutable unsigned max_depth; +}; + +/* Describes a pair of references used in an access by built-in + functions like memcpy. */ +struct access_data +{ + /* Set the access to at most MAXWRITE and MAXREAD bytes, and + at least 1 when MINWRITE or MINREAD, respectively, is set. */ + access_data (tree expr, access_mode mode, + tree maxwrite = NULL_TREE, bool minwrite = false, + tree maxread = NULL_TREE, bool minread = false) + : call (expr), + dst (maxwrite, minwrite), src (maxread, minread), mode (mode) { } + + /* Built-in function call. */ + tree call; + /* Destination and source of the access. */ + access_ref dst, src; + /* Read-only for functions like memcmp or strlen, write-only + for memset, read-write for memcpy or strcat. */ + access_mode mode; +}; + +class range_query; +extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, + range_query * = NULL); +extern tree gimple_parm_array_size (tree, wide_int[2], bool * = NULL); + +extern tree compute_objsize (tree, int, access_ref *, range_query * = NULL); +/* Legacy/transitional API. Should not be used in new code. */ +extern tree compute_objsize (tree, int, access_ref *, pointer_query *); +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, + range_query * = NULL); + +#endif // GCC_POINTER_QUERY_H diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h index aa9757a2fe99..1f5b1370a95a 100644 --- a/gcc/tree-pass.h +++ b/gcc/tree-pass.h @@ -428,6 +428,7 @@ extern gimple_opt_pass *make_pass_oacc_device_lower (gcc::context *ctxt); extern gimple_opt_pass *make_pass_omp_device_lower (gcc::context *ctxt); extern gimple_opt_pass *make_pass_object_sizes (gcc::context *ctxt); extern gimple_opt_pass *make_pass_early_object_sizes (gcc::context *ctxt); +extern gimple_opt_pass *make_pass_warn_access (gcc::context *ctxt); extern gimple_opt_pass *make_pass_warn_printf (gcc::context *ctxt); extern gimple_opt_pass *make_pass_strlen (gcc::context *ctxt); extern gimple_opt_pass *make_pass_fold_builtins (gcc::context *ctxt); diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 799c21fddde9..15391da81048 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see #include "ssa.h" #include "cgraph.h" #include "gimple-pretty-print.h" +#include "gimple-ssa-warn-access.h" #include "gimple-ssa-warn-restrict.h" #include "fold-const.h" #include "stor-layout.h" @@ -47,6 +48,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-ssa-strlen.h" #include "tree-hash-traits.h" #include "builtins.h" +#include "pointer-query.h" #include "target.h" #include "diagnostic-core.h" #include "diagnostic.h" diff --git a/gcc/tree.c b/gcc/tree.c index c621f870880c..e923e67b6942 100644 --- a/gcc/tree.c +++ b/gcc/tree.c @@ -14380,6 +14380,65 @@ valid_new_delete_pair_p (tree new_asm, tree delete_asm) return false; } +/* Return the zero-based number corresponding to the argument being + deallocated if FNDECL is a deallocation function or an out-of-bounds + value if it isn't. */ + +unsigned +fndecl_dealloc_argno (tree fndecl) +{ + /* A call to operator delete isn't recognized as one to a built-in. */ + if (DECL_IS_OPERATOR_DELETE_P (fndecl)) + { + if (DECL_IS_REPLACEABLE_OPERATOR (fndecl)) + return 0; + + /* Avoid placement delete that's not been inlined. */ + tree fname = DECL_ASSEMBLER_NAME (fndecl); + if (id_equal (fname, "_ZdlPvS_") // ordinary form + || id_equal (fname, "_ZdaPvS_")) // array form + return UINT_MAX; + return 0; + } + + /* TODO: Handle user-defined functions with attribute malloc? Handle + known non-built-ins like fopen? */ + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_FREE: + case BUILT_IN_REALLOC: + return 0; + default: + break; + } + return UINT_MAX; + } + + tree attrs = DECL_ATTRIBUTES (fndecl); + if (!attrs) + return UINT_MAX; + + for (tree atfree = attrs; + (atfree = lookup_attribute ("*dealloc", atfree)); + atfree = TREE_CHAIN (atfree)) + { + tree alloc = TREE_VALUE (atfree); + if (!alloc) + continue; + + tree pos = TREE_CHAIN (alloc); + if (!pos) + return 0; + + pos = TREE_VALUE (pos); + return TREE_INT_CST_LOW (pos) - 1; + } + + return UINT_MAX; +} + #if CHECKING_P namespace selftest { diff --git a/gcc/tree.h b/gcc/tree.h index 13c088cee054..972ceb370f87 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -6468,4 +6468,9 @@ extern void suppress_warning (tree, opt_code = all_warnings, bool = true) /* Copy warning disposition from one expression to another. */ extern void copy_warning (tree, const_tree); +/* Return the zero-based number corresponding to the argument being + deallocated if FNDECL is a deallocation function or an out-of-bounds + value if it isn't. */ +extern unsigned fndecl_dealloc_argno (tree); + #endif /* GCC_TREE_H */ -- 2.39.2