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
