Add 'no_builtin' function attribute

Message ID 20211102211458.10233-1-keithp@keithp.com
State New
Headers
Series Add 'no_builtin' function attribute |

Commit Message

Keith Packard Nov. 2, 2021, 9:14 p.m. UTC
  This attribute controls optimizations which make assumptions about the
semantics of builtin functions. Typical cases here are code which
match memcpy, calloc, sincos, or which call builtins like free.

This extends on things like the -ftree-loop-distribute-patterns
flag. That flag only covers converting loops into builtin calls, not
numerous other places where knowledge of builtin function semantics
changes the generated code.

The goal is to allow built-in functions to be declared by the compiler
and used directly by the application, but to disable optimizations
which take advantage of compiler knowledge about their semantics, and
to allow this optimization behavior to be changed for individual
functions.

One place where this behavior is especially useful is when compiling
the builtin functions that gcc knows about, as in the C
library. Currently, C library source code and build systems have
various kludges to work around the compilers operations in these
areas, using a combination of -fno-tree-loop-distribute-patterns,
-fno-builtins and even symbol aliases to keep GCC from generating
infinite recursions.

This can be applied globally to a file using the -fno-optimize-builtin
flag.

This disables optimizations which translate a sequence of builtin calls
into an equivalent sequence:

	void
	attribute((no_builtin))
	sincos(double x, double *s, double *c)
	{
		*s = sin(x);
		*c = cos(x);
	}

This also avoids converting loops into builtin calls like this:

	void *
	attribute((no_builtin))
	memcpy(void *__restrict__ dst, const void *__restrict__ src, size_t n)
	{
		char *d = dst;
		const char *s = src;

		while (n--)
			*d++ = *s++;
		return dst;
	}

As well as disabling analysis of memory lifetimes around free as in
this example:

	void *
	attribute((no_builtin))
	erase_and_free(void *ptr)
	{
		memset(ptr, '\0', malloc_usable_size(ptr));
		free(ptr);
	}

It also prevents converting builtin calls into inline code:

	void
	attribute((no_builtin))
	copy_fixed(char *dest)
	{
		strcpy(dest, "hello world");
	}

Clang has a more sophisticated version of this mechanism which
can disable specific builtins:

	double
	attribute((no_builtin("exp2")))
	exp2(double x)
	{
		return pow (2.0, x);
	}

The general approach in this change is to introduce checks in some
places where builtin functions are used to see if the specific
function is 'allowed' to be used for optimization, skipping the
optimization when the desired function has been disabled.

Three new functions, builtin_decl_implicit_opt_p,
builtin_decl_explicit_opt and builtin_decl_implicit_opt are introduced
which add checks for whether the compiler can assume standard
semantics for the specified function for purposes of
optimization. These are used throughout the compiler wherever
appropriate. Code which must use builtins for correct operation
(e.g. struct assignment) are not affected.

The machinery proposed here could be extended to support the
additional clang feature by extending the attribute parsing function
and creating a list of disabled builtins checked by the builtin_decl
functions described above.

Signed-off-by: Keith Packard <keithp@keithp.com>
---
 gcc/builtins.c               | 12 +++---
 gcc/c-family/c-attribs.c     | 68 ++++++++++++++++++++++++++++++++++
 gcc/common.opt               |  4 ++
 gcc/gimple-fold.c            | 72 ++++++++++++++++++------------------
 gcc/gimple-match-head.c      |  2 +-
 gcc/tree-loop-distribution.c |  7 ++++
 gcc/tree-ssa-alias.c         |  3 +-
 gcc/tree-ssa-strlen.c        | 48 ++++++++++++++----------
 gcc/tree-ssa-structalias.c   |  3 +-
 gcc/tree.h                   | 39 +++++++++++++++++++
 10 files changed, 194 insertions(+), 64 deletions(-)
  

Comments

Martin Sebor Nov. 3, 2021, 3:38 p.m. UTC | #1
On 11/2/21 3:14 PM, Keith Packard via Gcc-patches wrote:
> This attribute controls optimizations which make assumptions about the
> semantics of builtin functions. Typical cases here are code which
> match memcpy, calloc, sincos, or which call builtins like free.
> 
> This extends on things like the -ftree-loop-distribute-patterns
> flag. That flag only covers converting loops into builtin calls, not
> numerous other places where knowledge of builtin function semantics
> changes the generated code.
> 
> The goal is to allow built-in functions to be declared by the compiler
> and used directly by the application, but to disable optimizations
> which take advantage of compiler knowledge about their semantics, and
> to allow this optimization behavior to be changed for individual
> functions.
> 
> One place where this behavior is especially useful is when compiling
> the builtin functions that gcc knows about, as in the C
> library. Currently, C library source code and build systems have
> various kludges to work around the compilers operations in these
> areas, using a combination of -fno-tree-loop-distribute-patterns,
> -fno-builtins and even symbol aliases to keep GCC from generating
> infinite recursions.
> 
> This can be applied globally to a file using the -fno-optimize-builtin
> flag.

Can this option be used in attribute optimize?  If yes, what's
the advantage of also providing an atttribute?

A few more comments below.

> 
> This disables optimizations which translate a sequence of builtin calls
> into an equivalent sequence:
> 
> 	void
> 	attribute((no_builtin))
> 	sincos(double x, double *s, double *c)
> 	{
> 		*s = sin(x);
> 		*c = cos(x);
> 	}
> 
> This also avoids converting loops into builtin calls like this:
> 
> 	void *
> 	attribute((no_builtin))
> 	memcpy(void *__restrict__ dst, const void *__restrict__ src, size_t n)
> 	{
> 		char *d = dst;
> 		const char *s = src;
> 
> 		while (n--)
> 			*d++ = *s++;
> 		return dst;
> 	}
> 
> As well as disabling analysis of memory lifetimes around free as in
> this example:
> 
> 	void *
> 	attribute((no_builtin))
> 	erase_and_free(void *ptr)
> 	{
> 		memset(ptr, '\0', malloc_usable_size(ptr));
> 		free(ptr);
> 	}
> 
> It also prevents converting builtin calls into inline code:
> 
> 	void
> 	attribute((no_builtin))
> 	copy_fixed(char *dest)
> 	{
> 		strcpy(dest, "hello world");
> 	}
> 
> Clang has a more sophisticated version of this mechanism which
> can disable specific builtins:
> 
> 	double
> 	attribute((no_builtin("exp2")))
> 	exp2(double x)
> 	{
> 		return pow (2.0, x);
> 	}
> 
> The general approach in this change is to introduce checks in some
> places where builtin functions are used to see if the specific
> function is 'allowed' to be used for optimization, skipping the
> optimization when the desired function has been disabled.
> 
> Three new functions, builtin_decl_implicit_opt_p,
> builtin_decl_explicit_opt and builtin_decl_implicit_opt are introduced
> which add checks for whether the compiler can assume standard
> semantics for the specified function for purposes of
> optimization. These are used throughout the compiler wherever
> appropriate. Code which must use builtins for correct operation
> (e.g. struct assignment) are not affected.
> 
> The machinery proposed here could be extended to support the
> additional clang feature by extending the attribute parsing function
> and creating a list of disabled builtins checked by the builtin_decl
> functions described above.

It seems to me that as a matter of QOI, GCC should be able to
disable the expansion of built-ins to calls to themselves in
their bodies (I would view it as a bug if GCC expanded a byte
copying loop into a call to __builtin_memcpy in the body of
a function named memcpy, without issuing a warnings; but even
with a warning I'd hope it to do the sensible thing and avoid
introducing infinite recursion).  A compiler may not be able
to do that in calls made from those functions, and for that
the built-in expansion needs to be disabled explicitly.
-fno-builtin isn't good enough because it doesn't prevent GCC
from introiducing calls to __builtin_ functions, and that's
what this feature is for.  Do I understand correctly?

If we end up adding a new attribute (as opposed to relying
on attribute optimize) and the attribute is expected to have
an effect only on the definitions of functions and not also
on their callers, I would suggest to consider having
the handler check that it is, in fact, on a definiton and
not a declaration and issuing a warning that it has no effect
on a declaration, to avoid misunderstandings.

If either a new attribute or a new option is introduced they
need to be documented in the manual.  From your description
it's not clear to me if it's just about disabling the expansion
of the built-ins or if it's meant to prevent GCC from making
any assumptions about their semantics in calls to the annotated
functions.  That will likely be of interest to users and it
matters for diagnostics.

We will also need to add tests for the feature.  There are many
built-ins in GCC but the patch only affects a small subset of
them (e.g., I see it affects a few stdio and string functions
but I'm not sure the printf family are among them -- all those
handled in gimple-ssa-sprintf.c).

Besides its effect on codegen, I would also hope for a few test
cases for warnings, those issued by the strlen pass in particular,
to make sure their effects either are preserved or are disabled
(whichever is appropriate).

Martin

> 
> Signed-off-by: Keith Packard <keithp@keithp.com>
> ---
>   gcc/builtins.c               | 12 +++---
>   gcc/c-family/c-attribs.c     | 68 ++++++++++++++++++++++++++++++++++
>   gcc/common.opt               |  4 ++
>   gcc/gimple-fold.c            | 72 ++++++++++++++++++------------------
>   gcc/gimple-match-head.c      |  2 +-
>   gcc/tree-loop-distribution.c |  7 ++++
>   gcc/tree-ssa-alias.c         |  3 +-
>   gcc/tree-ssa-strlen.c        | 48 ++++++++++++++----------
>   gcc/tree-ssa-structalias.c   |  3 +-
>   gcc/tree.h                   | 39 +++++++++++++++++++
>   10 files changed, 194 insertions(+), 64 deletions(-)
> 
> diff --git a/gcc/builtins.c b/gcc/builtins.c
> index 7d0f61fc98b..d665ee716e8 100644
> --- a/gcc/builtins.c
> +++ b/gcc/builtins.c
> @@ -2061,7 +2061,7 @@ mathfn_built_in_1 (tree type, combined_fn fn, bool implicit_p)
>     if (fcode2 == END_BUILTINS)
>       return NULL_TREE;
>   
> -  if (implicit_p && !builtin_decl_implicit_p (fcode2))
> +  if (implicit_p && !builtin_decl_implicit_opt_p (fcode2))
>       return NULL_TREE;
>   
>     return builtin_decl_explicit (fcode2);
> @@ -3481,9 +3481,9 @@ expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
>     src = CALL_EXPR_ARG (exp, 1);
>   
>     /* If return value is ignored, transform stpcpy into strcpy.  */
> -  if (target == const0_rtx && builtin_decl_implicit (BUILT_IN_STRCPY))
> +  if (target == const0_rtx && builtin_decl_implicit_opt (BUILT_IN_STRCPY))
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>         tree result = build_call_nofold_loc (loc, fn, 2, dst, src);
>         return expand_expr (result, target, mode, EXPAND_NORMAL);
>       }
> @@ -8258,7 +8258,7 @@ fold_builtin_sincos (location_t loc,
>     if (!call)
>       {
>         if (!targetm.libc_has_function (function_c99_math_complex, type)
> -	  || !builtin_decl_implicit_p (fn))
> +	  || !builtin_decl_implicit_opt_p (fn))
>   	return NULL_TREE;
>         fndecl = builtin_decl_explicit (fn);
>         call = build_call_expr_loc (loc, fndecl, 1, arg0);
> @@ -9717,7 +9717,7 @@ fold_builtin_strpbrk (location_t loc, tree, tree s1, tree s2, tree type)
>     if (p2[1] != '\0')
>       return NULL_TREE;  /* Really call strpbrk.  */
>   
> -  fn = builtin_decl_implicit (BUILT_IN_STRCHR);
> +  fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
>     if (!fn)
>       return NULL_TREE;
>   
> @@ -9810,7 +9810,7 @@ fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2)
>     const char *p2 = c_getstr (s2);
>     if (p2 && *p2 == '\0')
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRLEN);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
>   
>         /* If the replacement _DECL isn't initialized, don't do the
>   	 transformation.  */
> diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
> index 007b928c54b..d5cfa9bc70d 100644
> --- a/gcc/c-family/c-attribs.c
> +++ b/gcc/c-family/c-attribs.c
> @@ -130,6 +130,7 @@ static tree handle_vector_size_attribute (tree *, tree, tree, int,
>   					  bool *) ATTRIBUTE_NONNULL(3);
>   static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
>   static tree handle_nonstring_attribute (tree *, tree, tree, int, bool *);
> +static tree handle_no_builtin_attribute (tree *, tree, tree, int, bool *);
>   static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
>   static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *);
>   static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
> @@ -536,6 +537,8 @@ const struct attribute_spec c_common_attribute_table[] =
>   			      handle_special_var_sec_attribute, attr_section_exclusions },
>     { "access",		      1, 3, false, true, true, false,
>   			      handle_access_attribute, NULL },
> +  { "no_builtin",	      0, 0, true, false, false, false,
> +			      handle_no_builtin_attribute, NULL },
>     /* Attributes used by Objective-C.  */
>     { "NSObject",		      0, 0, true, false, false, false,
>   			      handle_nsobject_attribute, NULL },
> @@ -5512,6 +5515,71 @@ handle_optimize_attribute (tree *node, tree name, tree args,
>     return NULL_TREE;
>   }
>   
> +/* Handle a "no_builtin" attribute */
> +
> +static tree
> +handle_no_builtin_attribute (tree *node, tree name, tree ARG_UNUSED (args),
> +			  int ARG_UNUSED (flags), bool *no_add_attrs)
> +{
> +  /* Ensure we have a function type.  */
> +  if (TREE_CODE (*node) != FUNCTION_DECL)
> +    {
> +      warning (OPT_Wattributes, "%qE attribute ignored", name);
> +      *no_add_attrs = true;
> +    }
> +  else
> +    {
> +      struct cl_optimization cur_opts;
> +      tree old_opts = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (*node);
> +
> +      /* Save current options.  */
> +      cl_optimization_save (&cur_opts, &global_options, &global_options_set);
> +      tree prev_target_node = build_target_option_node (&global_options,
> +							&global_options_set);
> +
> +      /* If we previously had some optimization options, use them as the
> +	 default.  */
> +      gcc_options *saved_global_options = NULL;
> +
> +      /* When #pragma GCC optimize pragma is used, it modifies global_options
> +	 without calling targetm.override_options_after_change.  That can leave
> +	 target flags inconsistent for comparison.  */
> +      if (flag_checking && optimization_current_node == optimization_default_node)
> +	{
> +	  saved_global_options = XNEW (gcc_options);
> +	  *saved_global_options = global_options;
> +	}
> +
> +      if (old_opts)
> +	cl_optimization_restore (&global_options, &global_options_set,
> +				 TREE_OPTIMIZATION (old_opts));
> +
> +      /* Set no_builtin option */
> +      flag_no_opt_builtin = TRUE;
> +
> +      DECL_FUNCTION_SPECIFIC_OPTIMIZATION (*node)
> +	= build_optimization_node (&global_options, &global_options_set);
> +      tree target_node = build_target_option_node (&global_options,
> +						   &global_options_set);
> +      if (prev_target_node != target_node)
> +	DECL_FUNCTION_SPECIFIC_TARGET (*node) = target_node;
> +
> +      /* Restore current options.  */
> +      cl_optimization_restore (&global_options, &global_options_set,
> +			       &cur_opts);
> +      cl_target_option_restore (&global_options, &global_options_set,
> +				TREE_TARGET_OPTION (prev_target_node));
> +
> +      if (saved_global_options != NULL)
> +	{
> +	  cl_optimization_compare (saved_global_options, &global_options);
> +	  free (saved_global_options);
> +	}
> +    }
> +
> +  return NULL_TREE;
> +}
> +
>   /* Handle a "no_split_stack" attribute.  */
>   
>   static tree
> diff --git a/gcc/common.opt b/gcc/common.opt
> index eeba1a727f2..a5ec18e82f5 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -2154,6 +2154,10 @@ fsave-optimization-record
>   Common Var(flag_save_optimization_record) Optimization
>   Write a SRCFILE.opt-record.json file detailing what optimizations were performed.
>   
> +foptimize-builtin
> +Common Var(flag_no_opt_builtin, 0) Optimization
> +Enable optimizations based on known semantics for builtin functions.
> +
>   foptimize-register-move
>   Common Ignore
>   Does nothing. Preserved for backward compatibility.
> diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
> index 6e25a7c05db..b054f4248ce 100644
> --- a/gcc/gimple-fold.c
> +++ b/gcc/gimple-fold.c
> @@ -1071,7 +1071,7 @@ gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
>   		  && (MIN (src_align, dest_align) / BITS_PER_UNIT
>   		      >= tree_to_uhwi (len))))
>   	    {
> -	      tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +	      tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>   	      if (!fn)
>   		return false;
>   	      gimple_call_set_fndecl (stmt, fn);
> @@ -1125,7 +1125,7 @@ gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
>   	      else
>   		return false;
>   
> -	      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +	      fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>   	      if (!fn)
>   		return false;
>   	      gimple_call_set_fndecl (stmt, fn);
> @@ -1148,7 +1148,7 @@ gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
>   	      if (!refs_may_alias_p_1 (&destr, &srcr, false))
>   		{
>   		  tree fn;
> -		  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +		  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>   		  if (!fn)
>   		    return false;
>   		  gimple_call_set_fndecl (stmt, fn);
> @@ -1360,7 +1360,7 @@ done:
>   static bool
>   gimple_fold_builtin_bcmp (gimple_stmt_iterator *gsi)
>   {
> -  tree fn = builtin_decl_implicit (BUILT_IN_MEMCMP);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCMP);
>   
>     if (!fn)
>       return false;
> @@ -1384,7 +1384,7 @@ gimple_fold_builtin_bcmp (gimple_stmt_iterator *gsi)
>   static bool
>   gimple_fold_builtin_bcopy (gimple_stmt_iterator *gsi)
>   {
> -  tree fn = builtin_decl_implicit (BUILT_IN_MEMMOVE);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMMOVE);
>   
>     if (!fn)
>       return false;
> @@ -1411,7 +1411,7 @@ gimple_fold_builtin_bcopy (gimple_stmt_iterator *gsi)
>   static bool
>   gimple_fold_builtin_bzero (gimple_stmt_iterator *gsi)
>   {
> -  tree fn = builtin_decl_implicit (BUILT_IN_MEMSET);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMSET);
>   
>     if (!fn)
>       return false;
> @@ -2067,7 +2067,7 @@ gimple_fold_builtin_strcpy (gimple_stmt_iterator *gsi,
>     if (optimize_function_for_size_p (cfun))
>       return false;
>   
> -  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>     if (!fn)
>       return false;
>   
> @@ -2158,7 +2158,7 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
>     maybe_diag_stxncpy_trunc (*gsi, src, len);
>   
>     /* OK transform into builtin memcpy.  */
> -  tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>     if (!fn)
>       return false;
>   
> @@ -2219,7 +2219,7 @@ gimple_fold_builtin_strchr (gimple_stmt_iterator *gsi, bool is_strrchr)
>     /* Transform strrchr (s, 0) to strchr (s, 0) when optimizing for size.  */
>     if (is_strrchr && optimize_function_for_size_p (cfun))
>       {
> -      tree strchr_fn = builtin_decl_implicit (BUILT_IN_STRCHR);
> +      tree strchr_fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
>   
>         if (strchr_fn)
>   	{
> @@ -2232,7 +2232,7 @@ gimple_fold_builtin_strchr (gimple_stmt_iterator *gsi, bool is_strrchr)
>       }
>   
>     tree len;
> -  tree strlen_fn = builtin_decl_implicit (BUILT_IN_STRLEN);
> +  tree strlen_fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
>   
>     if (!strlen_fn)
>       return false;
> @@ -2314,7 +2314,7 @@ gimple_fold_builtin_strstr (gimple_stmt_iterator *gsi)
>     /* Transform strstr (x, "c") into strchr (x, 'c').  */
>     if (q[1] == '\0')
>       {
> -      tree strchr_fn = builtin_decl_implicit (BUILT_IN_STRCHR);
> +      tree strchr_fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
>         if (strchr_fn)
>   	{
>   	  tree c = build_int_cst (integer_type_node, q[0]);
> @@ -2365,8 +2365,8 @@ gimple_fold_builtin_strcat (gimple_stmt_iterator *gsi, tree dst, tree src)
>   
>     /* See if we can store by pieces into (dst + strlen(dst)).  */
>     tree newdst;
> -  tree strlen_fn = builtin_decl_implicit (BUILT_IN_STRLEN);
> -  tree memcpy_fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +  tree strlen_fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
> +  tree memcpy_fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>   
>     if (!strlen_fn || !memcpy_fn)
>       return false;
> @@ -2445,7 +2445,7 @@ gimple_fold_builtin_strcat_chk (gimple_stmt_iterator *gsi)
>       return false;
>   
>     /* If __builtin_strcat_chk is used, assume strcat is available.  */
> -  fn = builtin_decl_explicit (BUILT_IN_STRCAT);
> +  fn = builtin_decl_explicit_opt (BUILT_IN_STRCAT);
>     if (!fn)
>       return false;
>   
> @@ -2530,7 +2530,7 @@ gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
>   	suppress_warning (stmt, OPT_Wstringop_overflow_);
>       }
>   
> -  tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCAT);
>   
>     /* If the replacement _DECL isn't initialized, don't do the
>        transformation.  */
> @@ -2578,7 +2578,7 @@ gimple_fold_builtin_strncat_chk (gimple_stmt_iterator *gsi)
>   	  && ! tree_int_cst_lt (len, src_len))
>   	{
>   	  /* If LEN >= strlen (SRC), optimize into __strcat_chk.  */
> -	  fn = builtin_decl_explicit (BUILT_IN_STRCAT_CHK);
> +	  fn = builtin_decl_explicit_opt (BUILT_IN_STRCAT_CHK);
>   	  if (!fn)
>   	    return false;
>   
> @@ -2590,7 +2590,7 @@ gimple_fold_builtin_strncat_chk (gimple_stmt_iterator *gsi)
>       }
>   
>     /* If __builtin_strncat_chk is used, assume strncat is available.  */
> -  fn = builtin_decl_explicit (BUILT_IN_STRNCAT);
> +  fn = builtin_decl_explicit_opt (BUILT_IN_STRNCAT);
>     if (!fn)
>       return false;
>   
> @@ -2829,7 +2829,7 @@ gimple_fold_builtin_string_compare (gimple_stmt_iterator *gsi)
>         && ((p2 && len2 < bound && len2 == nulpos2)
>   	  || (p1 && len1 < bound && len1 == nulpos1)))
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRCMP);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCMP);
>         if (!fn)
>           return false;
>         gimple *repl = gimple_build_call (fn, 2, str1, str2);
> @@ -2927,11 +2927,11 @@ gimple_fold_builtin_fputs (gimple_stmt_iterator *gsi,
>     /* If we're using an unlocked function, assume the other unlocked
>        functions exist explicitly.  */
>     tree const fn_fputc = (unlocked
> -			 ? builtin_decl_explicit (BUILT_IN_FPUTC_UNLOCKED)
> -			 : builtin_decl_implicit (BUILT_IN_FPUTC));
> +			 ? builtin_decl_explicit_opt (BUILT_IN_FPUTC_UNLOCKED)
> +			 : builtin_decl_implicit_opt (BUILT_IN_FPUTC));
>     tree const fn_fwrite = (unlocked
> -			  ? builtin_decl_explicit (BUILT_IN_FWRITE_UNLOCKED)
> -			  : builtin_decl_implicit (BUILT_IN_FWRITE));
> +			  ? builtin_decl_explicit_opt (BUILT_IN_FWRITE_UNLOCKED)
> +			  : builtin_decl_implicit_opt (BUILT_IN_FWRITE));
>   
>     /* If the return value is used, don't do the transformation.  */
>     if (gimple_call_lhs (stmt))
> @@ -3041,7 +3041,7 @@ gimple_fold_builtin_memory_chk (gimple_stmt_iterator *gsi,
>   		{
>   		  /* (void) __mempcpy_chk () can be optimized into
>   		     (void) __memcpy_chk ().  */
> -		  fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
> +		  fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
>   		  if (!fn)
>   		    return false;
>   
> @@ -3269,7 +3269,7 @@ gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
>     /* If the result is unused, replace stpcpy with strcpy.  */
>     if (gimple_call_lhs (stmt) == NULL_TREE)
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>         if (!fn)
>   	return false;
>         gimple_call_set_fndecl (stmt, fn);
> @@ -3309,7 +3309,7 @@ gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
>       return false;
>   
>     /* If the source has a known length replace stpcpy with memcpy.  */
> -  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>     if (!fn)
>       return false;
>   
> @@ -3544,7 +3544,7 @@ gimple_fold_builtin_sprintf (gimple_stmt_iterator *gsi)
>     if (!init_target_chars ())
>       return false;
>   
> -  tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +  tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>     if (!fn)
>       return false;
>   
> @@ -3679,7 +3679,7 @@ gimple_fold_builtin_snprintf (gimple_stmt_iterator *gsi)
>     /* If the format doesn't contain % args or %%, use strcpy.  */
>     if (strchr (fmt_str, target_percent) == NULL)
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>         if (!fn)
>   	return false;
>   
> @@ -3726,7 +3726,7 @@ gimple_fold_builtin_snprintf (gimple_stmt_iterator *gsi)
>     /* If the format is "%s", use strcpy if the result isn't used.  */
>     else if (fmt_str && strcmp (fmt_str, target_percent_s) == 0)
>       {
> -      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>         if (!fn)
>   	return false;
>   
> @@ -3809,13 +3809,13 @@ gimple_fold_builtin_fprintf (gimple_stmt_iterator *gsi,
>       {
>         /* If we're using an unlocked function, assume the other
>   	 unlocked functions exist explicitly.  */
> -      fn_fputc = builtin_decl_explicit (BUILT_IN_FPUTC_UNLOCKED);
> -      fn_fputs = builtin_decl_explicit (BUILT_IN_FPUTS_UNLOCKED);
> +      fn_fputc = builtin_decl_explicit_opt (BUILT_IN_FPUTC_UNLOCKED);
> +      fn_fputs = builtin_decl_explicit_opt (BUILT_IN_FPUTS_UNLOCKED);
>       }
>     else
>       {
> -      fn_fputc = builtin_decl_implicit (BUILT_IN_FPUTC);
> -      fn_fputs = builtin_decl_implicit (BUILT_IN_FPUTS);
> +      fn_fputc = builtin_decl_implicit_opt (BUILT_IN_FPUTC);
> +      fn_fputs = builtin_decl_implicit_opt (BUILT_IN_FPUTS);
>       }
>   
>     if (!init_target_chars ())
> @@ -3909,13 +3909,13 @@ gimple_fold_builtin_printf (gimple_stmt_iterator *gsi, tree fmt,
>       {
>         /* If we're using an unlocked function, assume the other
>   	 unlocked functions exist explicitly.  */
> -      fn_putchar = builtin_decl_explicit (BUILT_IN_PUTCHAR_UNLOCKED);
> -      fn_puts = builtin_decl_explicit (BUILT_IN_PUTS_UNLOCKED);
> +      fn_putchar = builtin_decl_explicit_opt (BUILT_IN_PUTCHAR_UNLOCKED);
> +      fn_puts = builtin_decl_explicit_opt (BUILT_IN_PUTS_UNLOCKED);
>       }
>     else
>       {
> -      fn_putchar = builtin_decl_implicit (BUILT_IN_PUTCHAR);
> -      fn_puts = builtin_decl_implicit (BUILT_IN_PUTS);
> +      fn_putchar = builtin_decl_implicit_opt (BUILT_IN_PUTCHAR);
> +      fn_puts = builtin_decl_implicit_opt (BUILT_IN_PUTS);
>       }
>   
>     if (!init_target_chars ())
> diff --git a/gcc/gimple-match-head.c b/gcc/gimple-match-head.c
> index 9d88b2f8551..82bdad1380e 100644
> --- a/gcc/gimple-match-head.c
> +++ b/gcc/gimple-match-head.c
> @@ -630,7 +630,7 @@ maybe_push_res_to_seq (gimple_match_op *res_op, gimple_seq *seq, tree res)
>         else
>   	{
>   	  /* Find the function we want to call.  */
> -	  tree decl = builtin_decl_implicit (as_builtin_fn (fn));
> +	  tree decl = builtin_decl_implicit_opt (as_builtin_fn (fn));
>   	  if (!decl)
>   	    return NULL;
>   
> diff --git a/gcc/tree-loop-distribution.c b/gcc/tree-loop-distribution.c
> index 583c01a42d8..469c22d63cb 100644
> --- a/gcc/tree-loop-distribution.c
> +++ b/gcc/tree-loop-distribution.c
> @@ -1716,6 +1716,9 @@ classify_builtin_st (loop_p loop, partition *partition, data_reference_p dr)
>     gimple *stmt = DR_STMT (dr);
>     tree base, size, rhs = gimple_assign_rhs1 (stmt);
>   
> +  if (!builtin_decl_implicit_opt_p(BUILT_IN_MEMSET))
> +    return;
> +
>     if (const_with_all_bytes_same (rhs) == -1
>         && (!INTEGRAL_TYPE_P (TREE_TYPE (rhs))
>   	  || (TYPE_MODE (TREE_TYPE (rhs))
> @@ -1762,6 +1765,10 @@ loop_distribution::classify_builtin_ldst (loop_p loop, struct graph *rdg,
>     tree base, size, src_base, src_size;
>     auto_vec<tree> dst_steps, src_steps;
>   
> +  if (!builtin_decl_implicit_opt_p(BUILT_IN_MEMMOVE) ||
> +      !builtin_decl_implicit_opt_p(BUILT_IN_MEMCPY))
> +    return;
> +
>     /* Compute access range of both load and store.  */
>     int res = compute_access_range (loop, dst_dr, &base, &size, &dst_steps);
>     if (res != 2)
> diff --git a/gcc/tree-ssa-alias.c b/gcc/tree-ssa-alias.c
> index ce667ff32b9..77cf209b235 100644
> --- a/gcc/tree-ssa-alias.c
> +++ b/gcc/tree-ssa-alias.c
> @@ -3370,7 +3370,8 @@ stmt_kills_ref_p (gimple *stmt, ao_ref *ref)
>       {
>         tree callee = gimple_call_fndecl (stmt);
>         if (callee != NULL_TREE
> -	  && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
> +	  && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
> +	  && builtin_decl_implicit_opt_p(DECL_FUNCTION_CODE (callee)))
>   	switch (DECL_FUNCTION_CODE (callee))
>   	  {
>   	  case BUILT_IN_FREE:
> diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
> index 2de7cb1a6a0..050d0573936 100644
> --- a/gcc/tree-ssa-strlen.c
> +++ b/gcc/tree-ssa-strlen.c
> @@ -860,12 +860,17 @@ get_string_length (strinfo *si)
>   	 with the same stmt.  If they were unshared before and transformation
>   	 has been already done, the handling of BUILT_IN_STPCPY{,_CHK} should
>   	 just compute the right length.  */
> +      if (!builtin_decl_implicit_opt_p (DECL_FUNCTION_CODE (callee)))
> +	return NULL;
> +
>         switch (DECL_FUNCTION_CODE (callee))
>   	{
>   	case BUILT_IN_STRCAT:
>   	case BUILT_IN_STRCAT_CHK:
>   	  gsi = gsi_for_stmt (stmt);
> -	  fn = builtin_decl_implicit (BUILT_IN_STRLEN);
> +	  fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
> +	  if (!fn)
> +	    return NULL;
>   	  gcc_assert (lhs == NULL_TREE);
>   	  tem = unshare_expr (gimple_call_arg (stmt, 0));
>   	  lenstmt = gimple_build_call (fn, 1, tem);
> @@ -889,11 +894,12 @@ get_string_length (strinfo *si)
>   	  /* FALLTHRU */
>   	case BUILT_IN_STRCPY:
>   	case BUILT_IN_STRCPY_CHK:
> -	  gcc_assert (builtin_decl_implicit_p (BUILT_IN_STPCPY));
>   	  if (gimple_call_num_args (stmt) == 2)
> -	    fn = builtin_decl_implicit (BUILT_IN_STPCPY);
> +	    fn = builtin_decl_implicit_opt (BUILT_IN_STPCPY);
>   	  else
> -	    fn = builtin_decl_explicit (BUILT_IN_STPCPY_CHK);
> +	    fn = builtin_decl_explicit_opt (BUILT_IN_STPCPY_CHK);
> +	  if (!fn)
> +	    return NULL;
>   	  gcc_assert (lhs == NULL_TREE);
>   	  if (dump_file && (dump_flags & TDF_DETAILS) != 0)
>   	    {
> @@ -1689,6 +1695,9 @@ valid_builtin_call (gimple *stmt)
>         && !gimple_builtin_call_types_compatible_p (stmt, decl))
>       return false;
>   
> +  if (!builtin_decl_implicit_opt_p(DECL_FUNCTION_CODE (callee)))
> +    return false;
> +
>     switch (DECL_FUNCTION_CODE (callee))
>       {
>       case BUILT_IN_MEMCMP:
> @@ -2519,7 +2528,7 @@ strlen_pass::handle_builtin_strcpy (built_in_function bcode)
>         {
>         case BUILT_IN_STRCPY:
>         case BUILT_IN_STRCPY_CHK:
> -	if (lhs != NULL_TREE || !builtin_decl_implicit_p (BUILT_IN_STPCPY))
> +	if (lhs != NULL_TREE || !builtin_decl_implicit_opt_p (BUILT_IN_STPCPY))
>   	  return;
>   	break;
>         case BUILT_IN_STPCPY:
> @@ -2640,12 +2649,12 @@ strlen_pass::handle_builtin_strcpy (built_in_function bcode)
>     switch (bcode)
>       {
>       case BUILT_IN_STRCPY:
> -      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +      fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>         if (lhs)
>   	ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx;
>         break;
>       case BUILT_IN_STRCPY_CHK:
> -      fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
> +      fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
>         if (lhs)
>   	ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx;
>         break;
> @@ -2653,7 +2662,7 @@ strlen_pass::handle_builtin_strcpy (built_in_function bcode)
>         /* This would need adjustment of the lhs (subtract one),
>   	 or detection that the trailing '\0' doesn't need to be
>   	 written, if it will be immediately overwritten.
> -      fn = builtin_decl_explicit (BUILT_IN_MEMPCPY);  */
> +      fn = builtin_decl_explicit_opt (BUILT_IN_MEMPCPY);  */
>         if (lhs)
>   	{
>   	  dsi->endptr = lhs;
> @@ -2664,7 +2673,7 @@ strlen_pass::handle_builtin_strcpy (built_in_function bcode)
>         /* This would need adjustment of the lhs (subtract one),
>   	 or detection that the trailing '\0' doesn't need to be
>   	 written, if it will be immediately overwritten.
> -      fn = builtin_decl_explicit (BUILT_IN_MEMPCPY_CHK);  */
> +      fn = builtin_decl_explicit_opt (BUILT_IN_MEMPCPY_CHK);  */
>         if (lhs)
>   	{
>   	  dsi->endptr = lhs;
> @@ -3540,7 +3549,7 @@ strlen_pass::handle_builtin_strcat (built_in_function bcode)
>   	 with length endptr - p if we need to compute the length
>   	 later on.  Don't do this transformation if we don't need
>   	 it.  */
> -      if (builtin_decl_implicit_p (BUILT_IN_STPCPY) && lhs == NULL_TREE)
> +      if (builtin_decl_implicit_opt_p (BUILT_IN_STPCPY) && lhs == NULL_TREE)
>   	{
>   	  if (didx == 0)
>   	    {
> @@ -3590,7 +3599,7 @@ strlen_pass::handle_builtin_strcat (built_in_function bcode)
>       {
>         dsi->nonzero_chars = NULL;
>         dsi->full_string_p = false;
> -      if (lhs == NULL_TREE && builtin_decl_implicit_p (BUILT_IN_STPCPY))
> +      if (lhs == NULL_TREE && builtin_decl_implicit_opt_p (BUILT_IN_STPCPY))
>   	dsi->dont_invalidate = true;
>       }
>   
> @@ -3610,15 +3619,15 @@ strlen_pass::handle_builtin_strcat (built_in_function bcode)
>       {
>       case BUILT_IN_STRCAT:
>         if (srclen != NULL_TREE)
> -	fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
> +	fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
>         else
> -	fn = builtin_decl_implicit (BUILT_IN_STRCPY);
> +	fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
>         break;
>       case BUILT_IN_STRCAT_CHK:
>         if (srclen != NULL_TREE)
> -	fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
> +	fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
>         else
> -	fn = builtin_decl_explicit (BUILT_IN_STRCPY_CHK);
> +	fn = builtin_decl_explicit_opt (BUILT_IN_STRCPY_CHK);
>         objsz = gimple_call_arg (stmt, 2);
>         break;
>       default:
> @@ -3798,11 +3807,12 @@ strlen_pass::handle_builtin_memset (bool *zero_write)
>     if (code1 == BUILT_IN_CALLOC)
>       /* Not touching alloc_stmt */ ;
>     else if (code1 == BUILT_IN_MALLOC
> -	   && operand_equal_p (memset_size, alloc_size, 0))
> +	   && operand_equal_p (memset_size, alloc_size, 0)
> +	   && builtin_decl_implicit_opt_p (BUILT_IN_CALLOC))
>       {
>         /* Replace the malloc + memset calls with calloc.  */
>         gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt);
> -      update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2,
> +      update_gimple_call (&gsi1, builtin_decl_implicit_opt (BUILT_IN_CALLOC), 2,
>   			  alloc_size, build_one_cst (size_type_node));
>         si1->nonzero_chars = build_int_cst (size_type_node, 0);
>         si1->full_string_p = true;
> @@ -4367,8 +4377,8 @@ strlen_pass::handle_builtin_string_cmp ()
>         /* If the known length is less than the size of the other array
>   	 and the strcmp result is only used to test equality to zero,
>   	 transform the call to the equivalent _eq call.  */
> -      if (tree fn = builtin_decl_implicit (bound < 0 ? BUILT_IN_STRCMP_EQ
> -					   : BUILT_IN_STRNCMP_EQ))
> +      if (tree fn = builtin_decl_implicit_opt (bound < 0 ? BUILT_IN_STRCMP_EQ
> +					       : BUILT_IN_STRNCMP_EQ))
>   	{
>   	  tree n = build_int_cst (size_type_node, cmpsiz);
>   	  update_gimple_call (&m_gsi, fn, 3, arg1, arg2, n);
> diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
> index 99072df0768..c88ac432331 100644
> --- a/gcc/tree-ssa-structalias.c
> +++ b/gcc/tree-ssa-structalias.c
> @@ -4415,7 +4415,8 @@ find_func_aliases_for_builtin_call (struct function *fn, gcall *t)
>     auto_vec<ce_s, 4> rhsc;
>     varinfo_t fi;
>   
> -  if (gimple_call_builtin_p (t, BUILT_IN_NORMAL))
> +  if (gimple_call_builtin_p (t, BUILT_IN_NORMAL) &&
> +      builtin_decl_implicit_opt_p (DECL_FUNCTION_CODE (fndecl)))
>       /* ???  All builtins that are handled here need to be handled
>          in the alias-oracle query functions explicitly!  */
>       switch (DECL_FUNCTION_CODE (fndecl))
> diff --git a/gcc/tree.h b/gcc/tree.h
> index 7542d97ce12..72b969e1684 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -5728,6 +5728,20 @@ builtin_decl_explicit (enum built_in_function fncode)
>     return builtin_info[(size_t)fncode].decl;
>   }
>   
> +/* Return the tree node for an explicit builtin function if the
> +   optimizer is allowed to use it or NULL.  Use this instead of
> +   builtin_decl_implicit in code that could introduce a new builtin
> +   call to perform optimization, but where such a call is not
> +   required */
> +
> +static inline tree
> +builtin_decl_explicit_opt (enum built_in_function fncode)
> +{
> +  if (flag_no_opt_builtin)
> +    return NULL_TREE;
> +  return builtin_decl_explicit (fncode);
> +}
> +
>   /* Return the tree node for an implicit builtin function or NULL.  */
>   static inline tree
>   builtin_decl_implicit (enum built_in_function fncode)
> @@ -5741,6 +5755,20 @@ builtin_decl_implicit (enum built_in_function fncode)
>     return builtin_info[uns_fncode].decl;
>   }
>   
> +/* Return the tree node for an implicit builtin function if the
> +   optimizer is allowed to use it or NULL. Use this instead of
> +   builtin_decl_implicit in code that could introduce a new builtin
> +   call to perform optimization, but where such a call is not
> +   required */
> +
> +static inline tree
> +builtin_decl_implicit_opt (enum built_in_function fncode)
> +{
> +  if (flag_no_opt_builtin)
> +    return NULL_TREE;
> +  return builtin_decl_implicit (fncode);
> +}
> +
>   /* Set explicit builtin function nodes and whether it is an implicit
>      function.  */
>   
> @@ -5805,6 +5833,17 @@ builtin_decl_implicit_p (enum built_in_function fncode)
>   	  && builtin_info[uns_fncode].implicit_p);
>   }
>   
> +/* Return whether the standard builtin function can be used
> +   implicitly. Use this instead of builtin_decl_implicit_p in code
> +   that could introduce a new builtin call to perform optimization,
> +   but where such a call is not required */
> +
> +static inline bool
> +builtin_decl_implicit_opt_p (enum built_in_function fncode)
> +{
> +  return !flag_no_opt_builtin && builtin_decl_implicit_p(fncode);
> +}
> +
>   /* Return whether the standard builtin function was declared.  */
>   
>   static inline bool
>
  
Keith Packard Nov. 3, 2021, 4:43 p.m. UTC | #2
Martin Sebor <msebor@gmail.com> writes:

> Can this option be used in attribute optimize?  If yes, what's
> the advantage of also providing an atttribute?

Compatibility with the clang attribute.

> It seems to me that as a matter of QOI, GCC should be able to
> disable the expansion of built-ins to calls to themselves in
> their bodies (I would view it as a bug if GCC expanded a byte
> copying loop into a call to __builtin_memcpy in the body of
> a function named memcpy, without issuing a warnings; but even
> with a warning I'd hope it to do the sensible thing and avoid
> introducing infinite recursion).

That's a pretty sensible plan and would resolve many of the issues
caused by these optimizations while implementing libc itself. Of course,
that wouldn't fix other places where the compiler is using knowledge of
builtin semantics to drive optimization (like the memset call before
free).

> A compiler may not be able
> to do that in calls made from those functions, and for that
> the built-in expansion needs to be disabled explicitly.
> -fno-builtin isn't good enough because it doesn't prevent GCC
> from introiducing calls to __builtin_ functions, and that's
> what this feature is for.  Do I understand correctly?

-fno-builtin has two problems:

 1. Cannot be used in source code, only on the command line.

 2. Also disables explicit use of builtin functions.

> If we end up adding a new attribute (as opposed to relying
> on attribute optimize) and the attribute is expected to have
> an effect only on the definitions of functions and not also
> on their callers, I would suggest to consider having
> the handler check that it is, in fact, on a definiton and
> not a declaration and issuing a warning that it has no effect
> on a declaration, to avoid misunderstandings.

Hrm. I copied the code from the 'optimize' attribute to make this have
the same effect; as above, this new attribute offers clang compatibility
for something that can also be done with the existing optimize
attribute.

> If either a new attribute or a new option is introduced they
> need to be documented in the manual.  From your description
> it's not clear to me if it's just about disabling the expansion
> of the built-ins or if it's meant to prevent GCC from making
> any assumptions about their semantics in calls to the annotated
> functions.  That will likely be of interest to users and it
> matters for diagnostics.

Yes, I definitely need to add documentation. I'm actually using this
thread to help clarify precisely what those semantics should be; it's a
pretty subtle operation when compared with -fno-builtin.

> We will also need to add tests for the feature.  There are many
> built-ins in GCC but the patch only affects a small subset of
> them (e.g., I see it affects a few stdio and string functions
> but I'm not sure the printf family are among them -- all those
> handled in gimple-ssa-sprintf.c).

Yeah, I can get started on tests.

Keeping this operation from affecting things that it shouldn't while
hitting everything it should is a bit more difficult than I had hoped; I
suspect we'll be finding cases for some time unless I can discover an
easier place to hook this into.

> Besides its effect on codegen, I would also hope for a few test
> cases for warnings, those issued by the strlen pass in particular,
> to make sure their effects either are preserved or are disabled
> (whichever is appropriate).

Because the goal is to have the optimizer "forget" about the semantics of
builtins, but let them still be used when explicitly named in the code,
these warnings would still need to be enabled, so I left them in.

I think we need to get a clear description of what this attribute should
do before making many more changes in the code. Here's a first stab:

 no_builtin

        This attribute directs GCC to not use knowledge of the semantics
        of built-in functions to direct code optimization.  GCC will not
        add or remove usage of builtin functions, or restructure code in
        ways that depend on built-in knowledge of their
        operation. Explicit use of builtin functions will still use any
        internal implementations.

        In contrast to -fno-builtin, the no_builtin attribute does not
        disable declaration of the built-in functions, so messages
        related to use and definition will still be enabled, and they
        can still be used when named explicitly. In addition, the
        no_builtin attribute can be applied on a function-at-a-time
        level, while -fno-builtin must be specified on the command line.
  

Patch

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 7d0f61fc98b..d665ee716e8 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -2061,7 +2061,7 @@  mathfn_built_in_1 (tree type, combined_fn fn, bool implicit_p)
   if (fcode2 == END_BUILTINS)
     return NULL_TREE;
 
-  if (implicit_p && !builtin_decl_implicit_p (fcode2))
+  if (implicit_p && !builtin_decl_implicit_opt_p (fcode2))
     return NULL_TREE;
 
   return builtin_decl_explicit (fcode2);
@@ -3481,9 +3481,9 @@  expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
   src = CALL_EXPR_ARG (exp, 1);
 
   /* If return value is ignored, transform stpcpy into strcpy.  */
-  if (target == const0_rtx && builtin_decl_implicit (BUILT_IN_STRCPY))
+  if (target == const0_rtx && builtin_decl_implicit_opt (BUILT_IN_STRCPY))
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
       tree result = build_call_nofold_loc (loc, fn, 2, dst, src);
       return expand_expr (result, target, mode, EXPAND_NORMAL);
     }
@@ -8258,7 +8258,7 @@  fold_builtin_sincos (location_t loc,
   if (!call)
     {
       if (!targetm.libc_has_function (function_c99_math_complex, type)
-	  || !builtin_decl_implicit_p (fn))
+	  || !builtin_decl_implicit_opt_p (fn))
 	return NULL_TREE;
       fndecl = builtin_decl_explicit (fn);
       call = build_call_expr_loc (loc, fndecl, 1, arg0);
@@ -9717,7 +9717,7 @@  fold_builtin_strpbrk (location_t loc, tree, tree s1, tree s2, tree type)
   if (p2[1] != '\0')
     return NULL_TREE;  /* Really call strpbrk.  */
 
-  fn = builtin_decl_implicit (BUILT_IN_STRCHR);
+  fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
   if (!fn)
     return NULL_TREE;
 
@@ -9810,7 +9810,7 @@  fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2)
   const char *p2 = c_getstr (s2);
   if (p2 && *p2 == '\0')
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRLEN);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
 
       /* If the replacement _DECL isn't initialized, don't do the
 	 transformation.  */
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 007b928c54b..d5cfa9bc70d 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -130,6 +130,7 @@  static tree handle_vector_size_attribute (tree *, tree, tree, int,
 					  bool *) ATTRIBUTE_NONNULL(3);
 static tree handle_nonnull_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nonstring_attribute (tree *, tree, tree, int, bool *);
+static tree handle_no_builtin_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
 static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *);
 static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
@@ -536,6 +537,8 @@  const struct attribute_spec c_common_attribute_table[] =
 			      handle_special_var_sec_attribute, attr_section_exclusions },
   { "access",		      1, 3, false, true, true, false,
 			      handle_access_attribute, NULL },
+  { "no_builtin",	      0, 0, true, false, false, false,
+			      handle_no_builtin_attribute, NULL },
   /* Attributes used by Objective-C.  */
   { "NSObject",		      0, 0, true, false, false, false,
 			      handle_nsobject_attribute, NULL },
@@ -5512,6 +5515,71 @@  handle_optimize_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
+/* Handle a "no_builtin" attribute */
+
+static tree
+handle_no_builtin_attribute (tree *node, tree name, tree ARG_UNUSED (args),
+			  int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+  /* Ensure we have a function type.  */
+  if (TREE_CODE (*node) != FUNCTION_DECL)
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }
+  else
+    {
+      struct cl_optimization cur_opts;
+      tree old_opts = DECL_FUNCTION_SPECIFIC_OPTIMIZATION (*node);
+
+      /* Save current options.  */
+      cl_optimization_save (&cur_opts, &global_options, &global_options_set);
+      tree prev_target_node = build_target_option_node (&global_options,
+							&global_options_set);
+
+      /* If we previously had some optimization options, use them as the
+	 default.  */
+      gcc_options *saved_global_options = NULL;
+
+      /* When #pragma GCC optimize pragma is used, it modifies global_options
+	 without calling targetm.override_options_after_change.  That can leave
+	 target flags inconsistent for comparison.  */
+      if (flag_checking && optimization_current_node == optimization_default_node)
+	{
+	  saved_global_options = XNEW (gcc_options);
+	  *saved_global_options = global_options;
+	}
+
+      if (old_opts)
+	cl_optimization_restore (&global_options, &global_options_set,
+				 TREE_OPTIMIZATION (old_opts));
+
+      /* Set no_builtin option */
+      flag_no_opt_builtin = TRUE;
+
+      DECL_FUNCTION_SPECIFIC_OPTIMIZATION (*node)
+	= build_optimization_node (&global_options, &global_options_set);
+      tree target_node = build_target_option_node (&global_options,
+						   &global_options_set);
+      if (prev_target_node != target_node)
+	DECL_FUNCTION_SPECIFIC_TARGET (*node) = target_node;
+
+      /* Restore current options.  */
+      cl_optimization_restore (&global_options, &global_options_set,
+			       &cur_opts);
+      cl_target_option_restore (&global_options, &global_options_set,
+				TREE_TARGET_OPTION (prev_target_node));
+
+      if (saved_global_options != NULL)
+	{
+	  cl_optimization_compare (saved_global_options, &global_options);
+	  free (saved_global_options);
+	}
+    }
+
+  return NULL_TREE;
+}
+
 /* Handle a "no_split_stack" attribute.  */
 
 static tree
diff --git a/gcc/common.opt b/gcc/common.opt
index eeba1a727f2..a5ec18e82f5 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2154,6 +2154,10 @@  fsave-optimization-record
 Common Var(flag_save_optimization_record) Optimization
 Write a SRCFILE.opt-record.json file detailing what optimizations were performed.
 
+foptimize-builtin
+Common Var(flag_no_opt_builtin, 0) Optimization
+Enable optimizations based on known semantics for builtin functions.
+
 foptimize-register-move
 Common Ignore
 Does nothing. Preserved for backward compatibility.
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index 6e25a7c05db..b054f4248ce 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -1071,7 +1071,7 @@  gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
 		  && (MIN (src_align, dest_align) / BITS_PER_UNIT
 		      >= tree_to_uhwi (len))))
 	    {
-	      tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+	      tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
 	      if (!fn)
 		return false;
 	      gimple_call_set_fndecl (stmt, fn);
@@ -1125,7 +1125,7 @@  gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
 	      else
 		return false;
 
-	      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+	      fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
 	      if (!fn)
 		return false;
 	      gimple_call_set_fndecl (stmt, fn);
@@ -1148,7 +1148,7 @@  gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
 	      if (!refs_may_alias_p_1 (&destr, &srcr, false))
 		{
 		  tree fn;
-		  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+		  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
 		  if (!fn)
 		    return false;
 		  gimple_call_set_fndecl (stmt, fn);
@@ -1360,7 +1360,7 @@  done:
 static bool
 gimple_fold_builtin_bcmp (gimple_stmt_iterator *gsi)
 {
-  tree fn = builtin_decl_implicit (BUILT_IN_MEMCMP);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCMP);
 
   if (!fn)
     return false;
@@ -1384,7 +1384,7 @@  gimple_fold_builtin_bcmp (gimple_stmt_iterator *gsi)
 static bool
 gimple_fold_builtin_bcopy (gimple_stmt_iterator *gsi)
 {
-  tree fn = builtin_decl_implicit (BUILT_IN_MEMMOVE);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMMOVE);
 
   if (!fn)
     return false;
@@ -1411,7 +1411,7 @@  gimple_fold_builtin_bcopy (gimple_stmt_iterator *gsi)
 static bool
 gimple_fold_builtin_bzero (gimple_stmt_iterator *gsi)
 {
-  tree fn = builtin_decl_implicit (BUILT_IN_MEMSET);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMSET);
 
   if (!fn)
     return false;
@@ -2067,7 +2067,7 @@  gimple_fold_builtin_strcpy (gimple_stmt_iterator *gsi,
   if (optimize_function_for_size_p (cfun))
     return false;
 
-  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
   if (!fn)
     return false;
 
@@ -2158,7 +2158,7 @@  gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
   maybe_diag_stxncpy_trunc (*gsi, src, len);
 
   /* OK transform into builtin memcpy.  */
-  tree fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
   if (!fn)
     return false;
 
@@ -2219,7 +2219,7 @@  gimple_fold_builtin_strchr (gimple_stmt_iterator *gsi, bool is_strrchr)
   /* Transform strrchr (s, 0) to strchr (s, 0) when optimizing for size.  */
   if (is_strrchr && optimize_function_for_size_p (cfun))
     {
-      tree strchr_fn = builtin_decl_implicit (BUILT_IN_STRCHR);
+      tree strchr_fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
 
       if (strchr_fn)
 	{
@@ -2232,7 +2232,7 @@  gimple_fold_builtin_strchr (gimple_stmt_iterator *gsi, bool is_strrchr)
     }
 
   tree len;
-  tree strlen_fn = builtin_decl_implicit (BUILT_IN_STRLEN);
+  tree strlen_fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
 
   if (!strlen_fn)
     return false;
@@ -2314,7 +2314,7 @@  gimple_fold_builtin_strstr (gimple_stmt_iterator *gsi)
   /* Transform strstr (x, "c") into strchr (x, 'c').  */
   if (q[1] == '\0')
     {
-      tree strchr_fn = builtin_decl_implicit (BUILT_IN_STRCHR);
+      tree strchr_fn = builtin_decl_implicit_opt (BUILT_IN_STRCHR);
       if (strchr_fn)
 	{
 	  tree c = build_int_cst (integer_type_node, q[0]);
@@ -2365,8 +2365,8 @@  gimple_fold_builtin_strcat (gimple_stmt_iterator *gsi, tree dst, tree src)
 
   /* See if we can store by pieces into (dst + strlen(dst)).  */
   tree newdst;
-  tree strlen_fn = builtin_decl_implicit (BUILT_IN_STRLEN);
-  tree memcpy_fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+  tree strlen_fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
+  tree memcpy_fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
 
   if (!strlen_fn || !memcpy_fn)
     return false;
@@ -2445,7 +2445,7 @@  gimple_fold_builtin_strcat_chk (gimple_stmt_iterator *gsi)
     return false;
 
   /* If __builtin_strcat_chk is used, assume strcat is available.  */
-  fn = builtin_decl_explicit (BUILT_IN_STRCAT);
+  fn = builtin_decl_explicit_opt (BUILT_IN_STRCAT);
   if (!fn)
     return false;
 
@@ -2530,7 +2530,7 @@  gimple_fold_builtin_strncat (gimple_stmt_iterator *gsi)
 	suppress_warning (stmt, OPT_Wstringop_overflow_);
     }
 
-  tree fn = builtin_decl_implicit (BUILT_IN_STRCAT);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCAT);
 
   /* If the replacement _DECL isn't initialized, don't do the
      transformation.  */
@@ -2578,7 +2578,7 @@  gimple_fold_builtin_strncat_chk (gimple_stmt_iterator *gsi)
 	  && ! tree_int_cst_lt (len, src_len))
 	{
 	  /* If LEN >= strlen (SRC), optimize into __strcat_chk.  */
-	  fn = builtin_decl_explicit (BUILT_IN_STRCAT_CHK);
+	  fn = builtin_decl_explicit_opt (BUILT_IN_STRCAT_CHK);
 	  if (!fn)
 	    return false;
 
@@ -2590,7 +2590,7 @@  gimple_fold_builtin_strncat_chk (gimple_stmt_iterator *gsi)
     }
 
   /* If __builtin_strncat_chk is used, assume strncat is available.  */
-  fn = builtin_decl_explicit (BUILT_IN_STRNCAT);
+  fn = builtin_decl_explicit_opt (BUILT_IN_STRNCAT);
   if (!fn)
     return false;
 
@@ -2829,7 +2829,7 @@  gimple_fold_builtin_string_compare (gimple_stmt_iterator *gsi)
       && ((p2 && len2 < bound && len2 == nulpos2)
 	  || (p1 && len1 < bound && len1 == nulpos1)))
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRCMP);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCMP);
       if (!fn)
         return false;
       gimple *repl = gimple_build_call (fn, 2, str1, str2);
@@ -2927,11 +2927,11 @@  gimple_fold_builtin_fputs (gimple_stmt_iterator *gsi,
   /* If we're using an unlocked function, assume the other unlocked
      functions exist explicitly.  */
   tree const fn_fputc = (unlocked
-			 ? builtin_decl_explicit (BUILT_IN_FPUTC_UNLOCKED)
-			 : builtin_decl_implicit (BUILT_IN_FPUTC));
+			 ? builtin_decl_explicit_opt (BUILT_IN_FPUTC_UNLOCKED)
+			 : builtin_decl_implicit_opt (BUILT_IN_FPUTC));
   tree const fn_fwrite = (unlocked
-			  ? builtin_decl_explicit (BUILT_IN_FWRITE_UNLOCKED)
-			  : builtin_decl_implicit (BUILT_IN_FWRITE));
+			  ? builtin_decl_explicit_opt (BUILT_IN_FWRITE_UNLOCKED)
+			  : builtin_decl_implicit_opt (BUILT_IN_FWRITE));
 
   /* If the return value is used, don't do the transformation.  */
   if (gimple_call_lhs (stmt))
@@ -3041,7 +3041,7 @@  gimple_fold_builtin_memory_chk (gimple_stmt_iterator *gsi,
 		{
 		  /* (void) __mempcpy_chk () can be optimized into
 		     (void) __memcpy_chk ().  */
-		  fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
+		  fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
 		  if (!fn)
 		    return false;
 
@@ -3269,7 +3269,7 @@  gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
   /* If the result is unused, replace stpcpy with strcpy.  */
   if (gimple_call_lhs (stmt) == NULL_TREE)
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
       if (!fn)
 	return false;
       gimple_call_set_fndecl (stmt, fn);
@@ -3309,7 +3309,7 @@  gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
     return false;
 
   /* If the source has a known length replace stpcpy with memcpy.  */
-  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+  fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
   if (!fn)
     return false;
 
@@ -3544,7 +3544,7 @@  gimple_fold_builtin_sprintf (gimple_stmt_iterator *gsi)
   if (!init_target_chars ())
     return false;
 
-  tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+  tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
   if (!fn)
     return false;
 
@@ -3679,7 +3679,7 @@  gimple_fold_builtin_snprintf (gimple_stmt_iterator *gsi)
   /* If the format doesn't contain % args or %%, use strcpy.  */
   if (strchr (fmt_str, target_percent) == NULL)
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
       if (!fn)
 	return false;
 
@@ -3726,7 +3726,7 @@  gimple_fold_builtin_snprintf (gimple_stmt_iterator *gsi)
   /* If the format is "%s", use strcpy if the result isn't used.  */
   else if (fmt_str && strcmp (fmt_str, target_percent_s) == 0)
     {
-      tree fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+      tree fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
       if (!fn)
 	return false;
 
@@ -3809,13 +3809,13 @@  gimple_fold_builtin_fprintf (gimple_stmt_iterator *gsi,
     {
       /* If we're using an unlocked function, assume the other
 	 unlocked functions exist explicitly.  */
-      fn_fputc = builtin_decl_explicit (BUILT_IN_FPUTC_UNLOCKED);
-      fn_fputs = builtin_decl_explicit (BUILT_IN_FPUTS_UNLOCKED);
+      fn_fputc = builtin_decl_explicit_opt (BUILT_IN_FPUTC_UNLOCKED);
+      fn_fputs = builtin_decl_explicit_opt (BUILT_IN_FPUTS_UNLOCKED);
     }
   else
     {
-      fn_fputc = builtin_decl_implicit (BUILT_IN_FPUTC);
-      fn_fputs = builtin_decl_implicit (BUILT_IN_FPUTS);
+      fn_fputc = builtin_decl_implicit_opt (BUILT_IN_FPUTC);
+      fn_fputs = builtin_decl_implicit_opt (BUILT_IN_FPUTS);
     }
 
   if (!init_target_chars ())
@@ -3909,13 +3909,13 @@  gimple_fold_builtin_printf (gimple_stmt_iterator *gsi, tree fmt,
     {
       /* If we're using an unlocked function, assume the other
 	 unlocked functions exist explicitly.  */
-      fn_putchar = builtin_decl_explicit (BUILT_IN_PUTCHAR_UNLOCKED);
-      fn_puts = builtin_decl_explicit (BUILT_IN_PUTS_UNLOCKED);
+      fn_putchar = builtin_decl_explicit_opt (BUILT_IN_PUTCHAR_UNLOCKED);
+      fn_puts = builtin_decl_explicit_opt (BUILT_IN_PUTS_UNLOCKED);
     }
   else
     {
-      fn_putchar = builtin_decl_implicit (BUILT_IN_PUTCHAR);
-      fn_puts = builtin_decl_implicit (BUILT_IN_PUTS);
+      fn_putchar = builtin_decl_implicit_opt (BUILT_IN_PUTCHAR);
+      fn_puts = builtin_decl_implicit_opt (BUILT_IN_PUTS);
     }
 
   if (!init_target_chars ())
diff --git a/gcc/gimple-match-head.c b/gcc/gimple-match-head.c
index 9d88b2f8551..82bdad1380e 100644
--- a/gcc/gimple-match-head.c
+++ b/gcc/gimple-match-head.c
@@ -630,7 +630,7 @@  maybe_push_res_to_seq (gimple_match_op *res_op, gimple_seq *seq, tree res)
       else
 	{
 	  /* Find the function we want to call.  */
-	  tree decl = builtin_decl_implicit (as_builtin_fn (fn));
+	  tree decl = builtin_decl_implicit_opt (as_builtin_fn (fn));
 	  if (!decl)
 	    return NULL;
 
diff --git a/gcc/tree-loop-distribution.c b/gcc/tree-loop-distribution.c
index 583c01a42d8..469c22d63cb 100644
--- a/gcc/tree-loop-distribution.c
+++ b/gcc/tree-loop-distribution.c
@@ -1716,6 +1716,9 @@  classify_builtin_st (loop_p loop, partition *partition, data_reference_p dr)
   gimple *stmt = DR_STMT (dr);
   tree base, size, rhs = gimple_assign_rhs1 (stmt);
 
+  if (!builtin_decl_implicit_opt_p(BUILT_IN_MEMSET))
+    return;
+
   if (const_with_all_bytes_same (rhs) == -1
       && (!INTEGRAL_TYPE_P (TREE_TYPE (rhs))
 	  || (TYPE_MODE (TREE_TYPE (rhs))
@@ -1762,6 +1765,10 @@  loop_distribution::classify_builtin_ldst (loop_p loop, struct graph *rdg,
   tree base, size, src_base, src_size;
   auto_vec<tree> dst_steps, src_steps;
 
+  if (!builtin_decl_implicit_opt_p(BUILT_IN_MEMMOVE) ||
+      !builtin_decl_implicit_opt_p(BUILT_IN_MEMCPY))
+    return;
+
   /* Compute access range of both load and store.  */
   int res = compute_access_range (loop, dst_dr, &base, &size, &dst_steps);
   if (res != 2)
diff --git a/gcc/tree-ssa-alias.c b/gcc/tree-ssa-alias.c
index ce667ff32b9..77cf209b235 100644
--- a/gcc/tree-ssa-alias.c
+++ b/gcc/tree-ssa-alias.c
@@ -3370,7 +3370,8 @@  stmt_kills_ref_p (gimple *stmt, ao_ref *ref)
     {
       tree callee = gimple_call_fndecl (stmt);
       if (callee != NULL_TREE
-	  && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
+	  && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+	  && builtin_decl_implicit_opt_p(DECL_FUNCTION_CODE (callee)))
 	switch (DECL_FUNCTION_CODE (callee))
 	  {
 	  case BUILT_IN_FREE:
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 2de7cb1a6a0..050d0573936 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -860,12 +860,17 @@  get_string_length (strinfo *si)
 	 with the same stmt.  If they were unshared before and transformation
 	 has been already done, the handling of BUILT_IN_STPCPY{,_CHK} should
 	 just compute the right length.  */
+      if (!builtin_decl_implicit_opt_p (DECL_FUNCTION_CODE (callee)))
+	return NULL;
+
       switch (DECL_FUNCTION_CODE (callee))
 	{
 	case BUILT_IN_STRCAT:
 	case BUILT_IN_STRCAT_CHK:
 	  gsi = gsi_for_stmt (stmt);
-	  fn = builtin_decl_implicit (BUILT_IN_STRLEN);
+	  fn = builtin_decl_implicit_opt (BUILT_IN_STRLEN);
+	  if (!fn)
+	    return NULL;
 	  gcc_assert (lhs == NULL_TREE);
 	  tem = unshare_expr (gimple_call_arg (stmt, 0));
 	  lenstmt = gimple_build_call (fn, 1, tem);
@@ -889,11 +894,12 @@  get_string_length (strinfo *si)
 	  /* FALLTHRU */
 	case BUILT_IN_STRCPY:
 	case BUILT_IN_STRCPY_CHK:
-	  gcc_assert (builtin_decl_implicit_p (BUILT_IN_STPCPY));
 	  if (gimple_call_num_args (stmt) == 2)
-	    fn = builtin_decl_implicit (BUILT_IN_STPCPY);
+	    fn = builtin_decl_implicit_opt (BUILT_IN_STPCPY);
 	  else
-	    fn = builtin_decl_explicit (BUILT_IN_STPCPY_CHK);
+	    fn = builtin_decl_explicit_opt (BUILT_IN_STPCPY_CHK);
+	  if (!fn)
+	    return NULL;
 	  gcc_assert (lhs == NULL_TREE);
 	  if (dump_file && (dump_flags & TDF_DETAILS) != 0)
 	    {
@@ -1689,6 +1695,9 @@  valid_builtin_call (gimple *stmt)
       && !gimple_builtin_call_types_compatible_p (stmt, decl))
     return false;
 
+  if (!builtin_decl_implicit_opt_p(DECL_FUNCTION_CODE (callee)))
+    return false;
+
   switch (DECL_FUNCTION_CODE (callee))
     {
     case BUILT_IN_MEMCMP:
@@ -2519,7 +2528,7 @@  strlen_pass::handle_builtin_strcpy (built_in_function bcode)
       {
       case BUILT_IN_STRCPY:
       case BUILT_IN_STRCPY_CHK:
-	if (lhs != NULL_TREE || !builtin_decl_implicit_p (BUILT_IN_STPCPY))
+	if (lhs != NULL_TREE || !builtin_decl_implicit_opt_p (BUILT_IN_STPCPY))
 	  return;
 	break;
       case BUILT_IN_STPCPY:
@@ -2640,12 +2649,12 @@  strlen_pass::handle_builtin_strcpy (built_in_function bcode)
   switch (bcode)
     {
     case BUILT_IN_STRCPY:
-      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+      fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
       if (lhs)
 	ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx;
       break;
     case BUILT_IN_STRCPY_CHK:
-      fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
+      fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
       if (lhs)
 	ssa_ver_to_stridx[SSA_NAME_VERSION (lhs)] = didx;
       break;
@@ -2653,7 +2662,7 @@  strlen_pass::handle_builtin_strcpy (built_in_function bcode)
       /* This would need adjustment of the lhs (subtract one),
 	 or detection that the trailing '\0' doesn't need to be
 	 written, if it will be immediately overwritten.
-      fn = builtin_decl_explicit (BUILT_IN_MEMPCPY);  */
+      fn = builtin_decl_explicit_opt (BUILT_IN_MEMPCPY);  */
       if (lhs)
 	{
 	  dsi->endptr = lhs;
@@ -2664,7 +2673,7 @@  strlen_pass::handle_builtin_strcpy (built_in_function bcode)
       /* This would need adjustment of the lhs (subtract one),
 	 or detection that the trailing '\0' doesn't need to be
 	 written, if it will be immediately overwritten.
-      fn = builtin_decl_explicit (BUILT_IN_MEMPCPY_CHK);  */
+      fn = builtin_decl_explicit_opt (BUILT_IN_MEMPCPY_CHK);  */
       if (lhs)
 	{
 	  dsi->endptr = lhs;
@@ -3540,7 +3549,7 @@  strlen_pass::handle_builtin_strcat (built_in_function bcode)
 	 with length endptr - p if we need to compute the length
 	 later on.  Don't do this transformation if we don't need
 	 it.  */
-      if (builtin_decl_implicit_p (BUILT_IN_STPCPY) && lhs == NULL_TREE)
+      if (builtin_decl_implicit_opt_p (BUILT_IN_STPCPY) && lhs == NULL_TREE)
 	{
 	  if (didx == 0)
 	    {
@@ -3590,7 +3599,7 @@  strlen_pass::handle_builtin_strcat (built_in_function bcode)
     {
       dsi->nonzero_chars = NULL;
       dsi->full_string_p = false;
-      if (lhs == NULL_TREE && builtin_decl_implicit_p (BUILT_IN_STPCPY))
+      if (lhs == NULL_TREE && builtin_decl_implicit_opt_p (BUILT_IN_STPCPY))
 	dsi->dont_invalidate = true;
     }
 
@@ -3610,15 +3619,15 @@  strlen_pass::handle_builtin_strcat (built_in_function bcode)
     {
     case BUILT_IN_STRCAT:
       if (srclen != NULL_TREE)
-	fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+	fn = builtin_decl_implicit_opt (BUILT_IN_MEMCPY);
       else
-	fn = builtin_decl_implicit (BUILT_IN_STRCPY);
+	fn = builtin_decl_implicit_opt (BUILT_IN_STRCPY);
       break;
     case BUILT_IN_STRCAT_CHK:
       if (srclen != NULL_TREE)
-	fn = builtin_decl_explicit (BUILT_IN_MEMCPY_CHK);
+	fn = builtin_decl_explicit_opt (BUILT_IN_MEMCPY_CHK);
       else
-	fn = builtin_decl_explicit (BUILT_IN_STRCPY_CHK);
+	fn = builtin_decl_explicit_opt (BUILT_IN_STRCPY_CHK);
       objsz = gimple_call_arg (stmt, 2);
       break;
     default:
@@ -3798,11 +3807,12 @@  strlen_pass::handle_builtin_memset (bool *zero_write)
   if (code1 == BUILT_IN_CALLOC)
     /* Not touching alloc_stmt */ ;
   else if (code1 == BUILT_IN_MALLOC
-	   && operand_equal_p (memset_size, alloc_size, 0))
+	   && operand_equal_p (memset_size, alloc_size, 0)
+	   && builtin_decl_implicit_opt_p (BUILT_IN_CALLOC))
     {
       /* Replace the malloc + memset calls with calloc.  */
       gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt);
-      update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2,
+      update_gimple_call (&gsi1, builtin_decl_implicit_opt (BUILT_IN_CALLOC), 2,
 			  alloc_size, build_one_cst (size_type_node));
       si1->nonzero_chars = build_int_cst (size_type_node, 0);
       si1->full_string_p = true;
@@ -4367,8 +4377,8 @@  strlen_pass::handle_builtin_string_cmp ()
       /* If the known length is less than the size of the other array
 	 and the strcmp result is only used to test equality to zero,
 	 transform the call to the equivalent _eq call.  */
-      if (tree fn = builtin_decl_implicit (bound < 0 ? BUILT_IN_STRCMP_EQ
-					   : BUILT_IN_STRNCMP_EQ))
+      if (tree fn = builtin_decl_implicit_opt (bound < 0 ? BUILT_IN_STRCMP_EQ
+					       : BUILT_IN_STRNCMP_EQ))
 	{
 	  tree n = build_int_cst (size_type_node, cmpsiz);
 	  update_gimple_call (&m_gsi, fn, 3, arg1, arg2, n);
diff --git a/gcc/tree-ssa-structalias.c b/gcc/tree-ssa-structalias.c
index 99072df0768..c88ac432331 100644
--- a/gcc/tree-ssa-structalias.c
+++ b/gcc/tree-ssa-structalias.c
@@ -4415,7 +4415,8 @@  find_func_aliases_for_builtin_call (struct function *fn, gcall *t)
   auto_vec<ce_s, 4> rhsc;
   varinfo_t fi;
 
-  if (gimple_call_builtin_p (t, BUILT_IN_NORMAL))
+  if (gimple_call_builtin_p (t, BUILT_IN_NORMAL) &&
+      builtin_decl_implicit_opt_p (DECL_FUNCTION_CODE (fndecl)))
     /* ???  All builtins that are handled here need to be handled
        in the alias-oracle query functions explicitly!  */
     switch (DECL_FUNCTION_CODE (fndecl))
diff --git a/gcc/tree.h b/gcc/tree.h
index 7542d97ce12..72b969e1684 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5728,6 +5728,20 @@  builtin_decl_explicit (enum built_in_function fncode)
   return builtin_info[(size_t)fncode].decl;
 }
 
+/* Return the tree node for an explicit builtin function if the
+   optimizer is allowed to use it or NULL.  Use this instead of
+   builtin_decl_implicit in code that could introduce a new builtin
+   call to perform optimization, but where such a call is not
+   required */
+
+static inline tree
+builtin_decl_explicit_opt (enum built_in_function fncode)
+{
+  if (flag_no_opt_builtin)
+    return NULL_TREE;
+  return builtin_decl_explicit (fncode);
+}
+
 /* Return the tree node for an implicit builtin function or NULL.  */
 static inline tree
 builtin_decl_implicit (enum built_in_function fncode)
@@ -5741,6 +5755,20 @@  builtin_decl_implicit (enum built_in_function fncode)
   return builtin_info[uns_fncode].decl;
 }
 
+/* Return the tree node for an implicit builtin function if the
+   optimizer is allowed to use it or NULL. Use this instead of
+   builtin_decl_implicit in code that could introduce a new builtin
+   call to perform optimization, but where such a call is not
+   required */
+
+static inline tree
+builtin_decl_implicit_opt (enum built_in_function fncode)
+{
+  if (flag_no_opt_builtin)
+    return NULL_TREE;
+  return builtin_decl_implicit (fncode);
+}
+
 /* Set explicit builtin function nodes and whether it is an implicit
    function.  */
 
@@ -5805,6 +5833,17 @@  builtin_decl_implicit_p (enum built_in_function fncode)
 	  && builtin_info[uns_fncode].implicit_p);
 }
 
+/* Return whether the standard builtin function can be used
+   implicitly. Use this instead of builtin_decl_implicit_p in code
+   that could introduce a new builtin call to perform optimization,
+   but where such a call is not required */
+
+static inline bool
+builtin_decl_implicit_opt_p (enum built_in_function fncode)
+{
+  return !flag_no_opt_builtin && builtin_decl_implicit_p(fncode);
+}
+
 /* Return whether the standard builtin function was declared.  */
 
 static inline bool