]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/builtins.c
* builtin-attrs.def (DEF_ATTR_FOR_INT): Add for 5 and 6.
[thirdparty/gcc.git] / gcc / builtins.c
index c966c0e0da306016b4dbb8b1da10ea85c61687c4..313170bbfbb9f6437a425fe178d05386f732efc2 100644 (file)
@@ -188,6 +188,18 @@ static tree fold_builtin_strspn (tree);
 static tree fold_builtin_strcspn (tree);
 static tree fold_builtin_sprintf (tree, int);
 
+static rtx expand_builtin_object_size (tree);
+static rtx expand_builtin_memory_chk (tree, rtx, enum machine_mode,
+                                     enum built_in_function);
+static void maybe_emit_chk_warning (tree, enum built_in_function);
+static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
+static tree fold_builtin_object_size (tree);
+static tree fold_builtin_strcat_chk (tree, tree);
+static tree fold_builtin_strncat_chk (tree, tree);
+static tree fold_builtin_sprintf_chk (tree, enum built_in_function);
+static tree fold_builtin_printf (tree, tree, bool, enum built_in_function);
+static tree fold_builtin_fprintf (tree, tree, bool, enum built_in_function);
+
 /* Return true if NODE should be considered for inline expansion regardless
    of the optimization level.  This means whenever a function is invoked with
    its "internal" name, which normally contains the prefix "__builtin".  */
@@ -2821,7 +2833,7 @@ expand_builtin_memcpy (tree exp, rtx target, enum machine_mode mode)
 }
 
 /* Expand a call to the mempcpy builtin, with arguments in ARGLIST.
-   Return 0 if we failed the caller should emit a normal call,
+   Return 0 if we failed; the caller should emit a normal call,
    otherwise try to get the result in TARGET, if convenient (and in
    mode MODE if that's convenient).  If ENDP is 0 return the
    destination pointer, if ENDP is 1 return the end pointer ala
@@ -2912,7 +2924,7 @@ expand_builtin_mempcpy (tree arglist, tree type, rtx target, enum machine_mode m
 }
 
 /* Expand expression EXP, which is a call to the memmove builtin.  Return 0
-   if we failed the caller should emit a normal call.  */
+   if we failed; the caller should emit a normal call.  */
 
 static rtx
 expand_builtin_memmove (tree arglist, tree type, rtx target,
@@ -6238,6 +6250,32 @@ expand_builtin (tree exp, rtx target, rtx subtarget, enum machine_mode mode,
       expand_builtin_synchronize ();
       return const0_rtx;
 
+    case BUILT_IN_OBJECT_SIZE:
+      return expand_builtin_object_size (exp);
+
+    case BUILT_IN_MEMCPY_CHK:
+    case BUILT_IN_MEMPCPY_CHK:
+    case BUILT_IN_MEMMOVE_CHK:
+    case BUILT_IN_MEMSET_CHK:
+      target = expand_builtin_memory_chk (exp, target, mode, fcode);
+      if (target)
+       return target;
+      break;
+
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STRNCPY_CHK:
+    case BUILT_IN_STRCAT_CHK:
+    case BUILT_IN_SNPRINTF_CHK:
+    case BUILT_IN_VSNPRINTF_CHK:
+      maybe_emit_chk_warning (exp, fcode);
+      break;
+
+    case BUILT_IN_SPRINTF_CHK:
+    case BUILT_IN_VSPRINTF_CHK:
+      maybe_emit_sprintf_chk_warning (exp, fcode);
+      break;
+
     default:   /* just do library call, if unknown builtin */
       break;
     }
@@ -8787,6 +8825,48 @@ fold_builtin_1 (tree fndecl, tree arglist, bool ignore)
     case BUILT_IN_VA_START:
       break;
 
+    case BUILT_IN_OBJECT_SIZE:
+      return fold_builtin_object_size (arglist);
+    case BUILT_IN_MEMCPY_CHK:
+    case BUILT_IN_MEMPCPY_CHK:
+    case BUILT_IN_MEMMOVE_CHK:
+    case BUILT_IN_MEMSET_CHK:
+      return fold_builtin_memory_chk (fndecl, arglist, NULL_TREE, ignore,
+                                     DECL_FUNCTION_CODE (fndecl));
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STPCPY_CHK:
+      return fold_builtin_stxcpy_chk (fndecl, arglist, NULL_TREE, ignore,
+                                     DECL_FUNCTION_CODE (fndecl));
+    case BUILT_IN_STRNCPY_CHK:
+      return fold_builtin_strncpy_chk (arglist, NULL_TREE);
+    case BUILT_IN_STRCAT_CHK:
+      return fold_builtin_strcat_chk (fndecl, arglist);
+    case BUILT_IN_STRNCAT_CHK:
+      return fold_builtin_strncat_chk (fndecl, arglist);
+    case BUILT_IN_SPRINTF_CHK:
+    case BUILT_IN_VSPRINTF_CHK:
+      return fold_builtin_sprintf_chk (arglist, DECL_FUNCTION_CODE (fndecl));
+    case BUILT_IN_SNPRINTF_CHK:
+    case BUILT_IN_VSNPRINTF_CHK:
+      return fold_builtin_snprintf_chk (arglist, NULL_TREE,
+                                       DECL_FUNCTION_CODE (fndecl));
+
+    case BUILT_IN_PRINTF:
+    case BUILT_IN_PRINTF_UNLOCKED:
+    case BUILT_IN_VPRINTF:
+    case BUILT_IN_PRINTF_CHK:
+    case BUILT_IN_VPRINTF_CHK:
+      return fold_builtin_printf (fndecl, arglist, ignore,
+                                 DECL_FUNCTION_CODE (fndecl));
+
+    case BUILT_IN_FPRINTF:
+    case BUILT_IN_FPRINTF_UNLOCKED:
+    case BUILT_IN_VFPRINTF:
+    case BUILT_IN_FPRINTF_CHK:
+    case BUILT_IN_VFPRINTF_CHK:
+      return fold_builtin_fprintf (fndecl, arglist, ignore,
+                                  DECL_FUNCTION_CODE (fndecl));
+
     default:
       break;
     }
@@ -9238,7 +9318,7 @@ fold_builtin_strncat (tree arglist)
       const char *p = c_getstr (src);
 
       /* If the requested length is zero, or the src parameter string
-          length is zero, return the dst parameter.  */
+        length is zero, return the dst parameter.  */
       if (integer_zerop (len) || (p && *p == '\0'))
         return omit_two_operands (TREE_TYPE (dst), dst, src, len);
 
@@ -9535,7 +9615,7 @@ fold_builtin_sprintf (tree arglist, int ignored)
      'sprintf (dest, "%s", orig)'.  */
   if (!validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)
       && !validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, POINTER_TYPE,
-                           VOID_TYPE))
+                           VOID_TYPE))
     return NULL_TREE;
 
   /* Get the destination string and the format specifier.  */
@@ -9599,3 +9679,1144 @@ fold_builtin_sprintf (tree arglist, int ignored)
   else
     return call;
 }
+
+/* Expand a call to __builtin_object_size.  */
+
+rtx
+expand_builtin_object_size (tree exp)
+{
+  tree ost;
+  int object_size_type;
+  tree fndecl = get_callee_fndecl (exp);
+  tree arglist = TREE_OPERAND (exp, 1);
+  location_t locus = EXPR_LOCATION (exp);
+
+  if (!validate_arglist (arglist, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+    {
+      error ("%Hfirst argument of %D must be a pointer, second integer constant",
+            &locus, fndecl);
+      expand_builtin_trap ();
+      return const0_rtx;
+    }
+
+  ost = TREE_VALUE (TREE_CHAIN (arglist));
+  STRIP_NOPS (ost);
+
+  if (TREE_CODE (ost) != INTEGER_CST
+      || tree_int_cst_sgn (ost) < 0
+      || compare_tree_int (ost, 3) > 0)
+    {
+      error ("%Hlast argument of %D is not integer constant between 0 and 3",
+            &locus, fndecl);
+      expand_builtin_trap ();
+      return const0_rtx;
+    }
+
+  object_size_type = tree_low_cst (ost, 0);
+
+  return object_size_type < 2 ? constm1_rtx : const0_rtx;
+}
+
+/* Expand EXP, a call to the __mem{cpy,pcpy,move,set}_chk builtin.
+   FCODE is the BUILT_IN_* to use.
+   Return 0 if we failed; the caller should emit a normal call,
+   otherwise try to get the result in TARGET, if convenient (and in
+   mode MODE if that's convenient).  */
+
+static rtx
+expand_builtin_memory_chk (tree exp, rtx target, enum machine_mode mode,
+                          enum built_in_function fcode)
+{
+  tree arglist = TREE_OPERAND (exp, 1);
+  tree dest, src, len, size;
+
+  if (!validate_arglist (arglist,
+                        POINTER_TYPE,
+                        fcode == BUILT_IN_MEMSET_CHK
+                        ? INTEGER_TYPE : POINTER_TYPE,
+                        INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  len = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (TREE_CHAIN (arglist))));
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (host_integerp (len, 1) || integer_all_onesp (size))
+    {
+      tree fn;
+
+      if (! integer_all_onesp (size) && tree_int_cst_lt (size, len))
+       {
+         location_t locus = EXPR_LOCATION (exp);
+         warning (0, "%Hcall to %D will always overflow destination buffer",
+                  &locus, get_callee_fndecl (exp));
+         return 0;
+       }
+
+      arglist = build_tree_list (NULL_TREE, len);
+      arglist = tree_cons (NULL_TREE, src, arglist);
+      arglist = tree_cons (NULL_TREE, dest, arglist);
+
+      fn = NULL_TREE;
+      /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume
+        mem{cpy,pcpy,move,set} is available.  */
+      switch (fcode)
+       {
+       case BUILT_IN_MEMCPY_CHK:
+         fn = built_in_decls[BUILT_IN_MEMCPY];
+         break;
+       case BUILT_IN_MEMPCPY_CHK:
+         fn = built_in_decls[BUILT_IN_MEMPCPY];
+         break;
+       case BUILT_IN_MEMMOVE_CHK:
+         fn = built_in_decls[BUILT_IN_MEMMOVE];
+         break;
+       case BUILT_IN_MEMSET_CHK:
+         fn = built_in_decls[BUILT_IN_MEMSET];
+         break;
+       default:
+         break;
+       }
+
+      if (! fn)
+       return 0;
+
+      fn = build_function_call_expr (fn, arglist);
+      if (TREE_CODE (fn) == CALL_EXPR)
+       CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp);
+      return expand_expr (fn, target, mode, EXPAND_NORMAL);
+    }
+  else if (fcode == BUILT_IN_MEMSET_CHK)
+    return 0;
+  else
+    {
+      unsigned int dest_align
+       = get_pointer_alignment (dest, BIGGEST_ALIGNMENT);
+
+      /* If DEST is not a pointer type, call the normal function.  */
+      if (dest_align == 0)
+       return 0;
+
+      /* If SRC and DEST are the same (and not volatile), do nothing.  */
+      if (operand_equal_p (src, dest, 0))
+       {
+         tree expr;
+
+         if (fcode != BUILT_IN_MEMPCPY_CHK)
+           {
+             /* Evaluate and ignore LEN in case it has side-effects.  */
+             expand_expr (len, const0_rtx, VOIDmode, EXPAND_NORMAL);
+             return expand_expr (dest, target, mode, EXPAND_NORMAL);
+           }
+
+         len = fold_convert (TREE_TYPE (dest), len);
+         expr = fold_build2 (PLUS_EXPR, TREE_TYPE (dest), dest, len);
+         return expand_expr (expr, target, mode, EXPAND_NORMAL);
+       }
+
+      /* __memmove_chk special case.  */
+      if (fcode == BUILT_IN_MEMMOVE_CHK)
+       {
+         unsigned int src_align
+           = get_pointer_alignment (src, BIGGEST_ALIGNMENT);
+
+         if (src_align == 0)
+           return 0;
+
+         /* If src is categorized for a readonly section we can use
+            normal __memcpy_chk.  */
+         if (readonly_data_expr (src))
+           {
+             tree fn = built_in_decls[BUILT_IN_MEMCPY_CHK];
+             if (!fn)
+               return 0;
+             fn = build_function_call_expr (fn, arglist);
+             if (TREE_CODE (fn) == CALL_EXPR)
+               CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp);
+             return expand_expr (fn, target, mode, EXPAND_NORMAL);
+           }
+       }
+      return 0;
+    }
+}
+
+/* Emit warning if a buffer overflow is detected at compile time.  */
+
+static void
+maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
+{
+  int arg_mask, is_strlen = 0;
+  tree arglist = TREE_OPERAND (exp, 1), a;
+  tree len, size;
+  location_t locus;
+
+  switch (fcode)
+    {
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STPCPY_CHK:
+    /* For __strcat_chk the warning will be emitted only if overflowing
+       by at least strlen (dest) + 1 bytes.  */
+    case BUILT_IN_STRCAT_CHK:
+      arg_mask = 6;
+      is_strlen = 1;
+      break;
+    case BUILT_IN_STRNCPY_CHK:
+      arg_mask = 12;
+      break;
+    case BUILT_IN_SNPRINTF_CHK:
+    case BUILT_IN_VSNPRINTF_CHK:
+      arg_mask = 10;
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  len = NULL_TREE;
+  size = NULL_TREE;
+  for (a = arglist; a && arg_mask; a = TREE_CHAIN (a), arg_mask >>= 1)
+    if (arg_mask & 1)
+      {
+       if (len)
+         size = a;
+       else
+         len = a;
+      }
+
+  if (!len || !size)
+    return;
+
+  len = TREE_VALUE (len);
+  size = TREE_VALUE (size);
+
+  if (! host_integerp (size, 1) || integer_all_onesp (size))
+    return;
+
+  if (is_strlen)
+    {
+      len = c_strlen (len, 1);
+      if (! len || ! host_integerp (len, 1) || tree_int_cst_lt (len, size))
+       return;
+    }
+  else if (! host_integerp (len, 1) || ! tree_int_cst_lt (size, len))
+    return;
+
+  locus = EXPR_LOCATION (exp);
+  warning (0, "%Hcall to %D will always overflow destination buffer",
+          &locus, get_callee_fndecl (exp));
+}
+
+/* Emit warning if a buffer overflow is detected at compile time
+   in __sprintf_chk/__vsprintf_chk calls.  */
+
+static void
+maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
+{
+  tree arglist = TREE_OPERAND (exp, 1);
+  tree dest, size, len, fmt, flag;
+  const char *fmt_str;
+
+  /* Verify the required arguments in the original call.  */
+  if (! arglist)
+    return;
+  dest = TREE_VALUE (arglist);
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return;
+  flag = TREE_VALUE (arglist);
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return;
+  size = TREE_VALUE (arglist);
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return;
+  fmt = TREE_VALUE (arglist);
+  arglist = TREE_CHAIN (arglist);
+
+  if (! host_integerp (size, 1) || integer_all_onesp (size))
+    return;
+
+  /* Check whether the format is a literal string constant.  */
+  fmt_str = c_getstr (fmt);
+  if (fmt_str == NULL)
+    return;
+
+  /* If the format doesn't contain % args or %%, we know its size.  */
+  if (strchr (fmt_str, '%') == 0)
+    len = build_int_cstu (size_type_node, strlen (fmt_str));
+  /* If the format is "%s" and first ... argument is a string literal,
+     we know it too.  */
+  else if (fcode == BUILT_IN_SPRINTF_CHK && strcmp (fmt_str, "%s") == 0)
+    {
+      tree arg;
+
+      if (! arglist)
+       return;
+      arg = TREE_VALUE (arglist);
+      if (! POINTER_TYPE_P (TREE_TYPE (arg)))
+       return;
+
+      len = c_strlen (arg, 1);
+      if (!len || ! host_integerp (len, 1))
+       return;
+    }
+  else
+    return;
+
+  if (! tree_int_cst_lt (len, size))
+    {
+      location_t locus = EXPR_LOCATION (exp);
+      warning (0, "%Hcall to %D will always overflow destination buffer",
+              &locus, get_callee_fndecl (exp));
+    }
+}
+
+/* Fold a call to __builtin_object_size, if possible.  */
+
+tree
+fold_builtin_object_size (tree arglist)
+{
+  tree ptr, ost, ret = 0;
+  int object_size_type;
+
+  if (!validate_arglist (arglist, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+    return 0;
+
+  ptr = TREE_VALUE (arglist);
+  ost = TREE_VALUE (TREE_CHAIN (arglist));
+  STRIP_NOPS (ost);
+
+  if (TREE_CODE (ost) != INTEGER_CST
+      || tree_int_cst_sgn (ost) < 0
+      || compare_tree_int (ost, 3) > 0)
+    return 0;
+
+  object_size_type = tree_low_cst (ost, 0);
+
+  /* __builtin_object_size doesn't evaluate side-effects in its arguments;
+     if there are any side-effects, it returns (size_t) -1 for types 0 and 1
+     and (size_t) 0 for types 2 and 3.  */
+  if (TREE_SIDE_EFFECTS (ptr))
+    return fold_convert (size_type_node,
+                        object_size_type < 2
+                        ? integer_minus_one_node : integer_zero_node);
+
+  if (TREE_CODE (ptr) == ADDR_EXPR)
+    ret = build_int_cstu (size_type_node,
+                       compute_builtin_object_size (ptr, object_size_type));
+
+  else if (TREE_CODE (ptr) == SSA_NAME)
+    {
+      unsigned HOST_WIDE_INT bytes;
+
+      /* If object size is not known yet, delay folding until
+       later.  Maybe subsequent passes will help determining
+       it.  */
+      bytes = compute_builtin_object_size (ptr, object_size_type);
+      if (bytes != (unsigned HOST_WIDE_INT) (object_size_type < 2
+                                            ? -1 : 0))
+       ret = build_int_cstu (size_type_node, bytes);
+    }
+
+  if (ret)
+    {
+      ret = force_fit_type (ret, -1, false, false);
+      if (TREE_CONSTANT_OVERFLOW (ret))
+       ret = 0;
+    }
+
+  return ret;
+}
+
+/* Fold a call to the __mem{cpy,pcpy,move,set}_chk builtin.
+   IGNORE is true, if return value can be ignored.  FCODE is the BUILT_IN_*
+   code of the builtin.  If MAXLEN is not NULL, it is maximum length
+   passed as third argument.  */
+
+tree
+fold_builtin_memory_chk (tree fndecl, tree arglist, tree maxlen, bool ignore,
+                        enum built_in_function fcode)
+{
+  tree dest, src, len, size, fn;
+
+  if (!validate_arglist (arglist,
+                        POINTER_TYPE,
+                        fcode == BUILT_IN_MEMSET_CHK
+                        ? INTEGER_TYPE : POINTER_TYPE,
+                        INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  /* Actually val for __memset_chk, but it doesn't matter.  */
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  len = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (TREE_CHAIN (arglist))));
+
+  /* If SRC and DEST are the same (and not volatile), return DEST
+     (resp. DEST+LEN for __mempcpy_chk).  */
+  if (fcode != BUILT_IN_MEMSET_CHK && operand_equal_p (src, dest, 0))
+    {
+      if (fcode != BUILT_IN_MEMPCPY_CHK)
+       return omit_one_operand (TREE_TYPE (TREE_TYPE (fndecl)), dest, len);
+      else
+       {
+         tree temp = fold_convert (TREE_TYPE (dest), len);
+         temp = fold_build2 (PLUS_EXPR, TREE_TYPE (dest), dest, temp);
+         return fold_convert (TREE_TYPE (TREE_TYPE (fndecl)), temp);
+       }
+    }
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (! integer_all_onesp (size))
+    {
+      if (! host_integerp (len, 1))
+       {
+         /* If LEN is not constant, try MAXLEN too.
+            For MAXLEN only allow optimizing into non-_ocs function
+            if SIZE is >= MAXLEN, never convert to __ocs_fail ().  */
+         if (maxlen == NULL_TREE || ! host_integerp (maxlen, 1))
+           {
+             if (fcode == BUILT_IN_MEMPCPY_CHK && ignore)
+               {
+                 /* (void) __mempcpy_chk () can be optimized into
+                    (void) __memcpy_chk ().  */
+                 fn = built_in_decls[BUILT_IN_MEMCPY_CHK];
+                 if (!fn)
+                   return 0;
+
+                 return build_function_call_expr (fn, arglist);
+               }
+             return 0;
+           }
+         len = maxlen;
+       }
+
+      if (tree_int_cst_lt (size, len))
+       return 0;
+    }
+
+  arglist = build_tree_list (NULL_TREE, len);
+  arglist = tree_cons (NULL_TREE, src, arglist);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  fn = NULL_TREE;
+  /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume
+     mem{cpy,pcpy,move,set} is available.  */
+  switch (fcode)
+    {
+    case BUILT_IN_MEMCPY_CHK:
+      fn = built_in_decls[BUILT_IN_MEMCPY];
+      break;
+    case BUILT_IN_MEMPCPY_CHK:
+      fn = built_in_decls[BUILT_IN_MEMPCPY];
+      break;
+    case BUILT_IN_MEMMOVE_CHK:
+      fn = built_in_decls[BUILT_IN_MEMMOVE];
+      break;
+    case BUILT_IN_MEMSET_CHK:
+      fn = built_in_decls[BUILT_IN_MEMSET];
+      break;
+    default:
+      break;
+    }
+
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to the __st[rp]cpy_chk builtin.
+   IGNORE is true, if return value can be ignored.  FCODE is the BUILT_IN_*
+   code of the builtin.  If MAXLEN is not NULL, it is maximum length of
+   strings passed as second argument.  */
+
+tree
+fold_builtin_stxcpy_chk (tree fndecl, tree arglist, tree maxlen, bool ignore,
+                        enum built_in_function fcode)
+{
+  tree dest, src, size, len, fn;
+
+  if (!validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE,
+                        VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+
+  /* If SRC and DEST are the same (and not volatile), return DEST.  */
+  if (fcode == BUILT_IN_STRCPY_CHK && operand_equal_p (src, dest, 0))
+    return fold_convert (TREE_TYPE (TREE_TYPE (fndecl)), dest);
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (! integer_all_onesp (size))
+    {
+      len = c_strlen (src, 1);
+      if (! len || ! host_integerp (len, 1))
+       {
+         /* If LEN is not constant, try MAXLEN too.
+            For MAXLEN only allow optimizing into non-_ocs function
+            if SIZE is >= MAXLEN, never convert to __ocs_fail ().  */
+         if (maxlen == NULL_TREE || ! host_integerp (maxlen, 1))
+           {
+             if (fcode == BUILT_IN_STPCPY_CHK)
+               {
+                 if (! ignore)
+                   return 0;
+
+                 /* If return value of __stpcpy_chk is ignored,
+                    optimize into __strcpy_chk.  */
+                 fn = built_in_decls[BUILT_IN_STRCPY_CHK];
+                 if (!fn)
+                   return 0;
+
+                 return build_function_call_expr (fn, arglist);
+               }
+
+             if (! len || TREE_SIDE_EFFECTS (len))
+               return 0;
+
+             /* If c_strlen returned something, but not a constant,
+                transform __strcpy_chk into __memcpy_chk.  */
+             fn = built_in_decls[BUILT_IN_MEMCPY_CHK];
+             if (!fn)
+               return 0;
+
+             len = size_binop (PLUS_EXPR, len, ssize_int (1));
+             arglist = build_tree_list (NULL_TREE, size);
+             arglist = tree_cons (NULL_TREE, len, arglist);
+             arglist = tree_cons (NULL_TREE, src, arglist);
+             arglist = tree_cons (NULL_TREE, dest, arglist);
+             return fold_convert (TREE_TYPE (TREE_TYPE (fndecl)),
+                                  build_function_call_expr (fn, arglist));
+           }
+         len = maxlen;
+       }
+      
+      if (! tree_int_cst_lt (len, size))
+       return 0;
+    }
+
+  arglist = build_tree_list (NULL_TREE, src);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_st{r,p}cpy_chk is used, assume st{r,p}cpy is available.  */
+  fn = built_in_decls[fcode == BUILT_IN_STPCPY_CHK
+                     ? BUILT_IN_STPCPY : BUILT_IN_STRCPY];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to the __strncpy_chk builtin.
+   If MAXLEN is not NULL, it is maximum length passed as third argument.  */
+
+tree
+fold_builtin_strncpy_chk (tree arglist, tree maxlen)
+{
+  tree dest, src, size, len, fn;
+
+  if (!validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE,
+                        INTEGER_TYPE, VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  len = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (TREE_CHAIN (arglist))));
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (! integer_all_onesp (size))
+    {
+      if (! host_integerp (len, 1))
+       {
+         /* If LEN is not constant, try MAXLEN too.
+            For MAXLEN only allow optimizing into non-_ocs function
+            if SIZE is >= MAXLEN, never convert to __ocs_fail ().  */
+         if (maxlen == NULL_TREE || ! host_integerp (maxlen, 1))
+           return 0;
+         len = maxlen;
+       }
+
+      if (tree_int_cst_lt (size, len))
+       return 0;
+    }
+
+  arglist = build_tree_list (NULL_TREE, len);
+  arglist = tree_cons (NULL_TREE, src, arglist);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_strncpy_chk is used, assume strncpy is available.  */
+  fn = built_in_decls[BUILT_IN_STRNCPY];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to the __strcat_chk builtin FNDECL with ARGLIST.  */
+
+static tree
+fold_builtin_strcat_chk (tree fndecl, tree arglist)
+{
+  tree dest, src, size, fn;
+  const char *p;
+
+  if (!validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE,
+                        VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+
+  p = c_getstr (src);
+  /* If the SRC parameter is "", return DEST.  */
+  if (p && *p == '\0')
+    return omit_one_operand (TREE_TYPE (TREE_TYPE (fndecl)), dest, src);
+
+  if (! host_integerp (size, 1) || ! integer_all_onesp (size))
+    return 0;
+
+  arglist = build_tree_list (NULL_TREE, src);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_strcat_chk is used, assume strcat is available.  */
+  fn = built_in_decls[BUILT_IN_STRCAT];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to the __strncat_chk builtin EXP.  */
+
+static tree
+fold_builtin_strncat_chk (tree fndecl, tree arglist)
+{
+  tree dest, src, size, len, fn;
+  const char *p;
+
+  if (!validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE,
+                        INTEGER_TYPE, VOID_TYPE))
+    return 0;
+
+  dest = TREE_VALUE (arglist);
+  src = TREE_VALUE (TREE_CHAIN (arglist));
+  len = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (arglist)));
+  size = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (TREE_CHAIN (arglist))));
+
+  p = c_getstr (src);
+  /* If the SRC parameter is "" or if LEN is 0, return DEST.  */
+  if (p && *p == '\0')
+    return omit_one_operand (TREE_TYPE (TREE_TYPE (fndecl)), dest, len);
+  else if (integer_zerop (len))
+    return omit_one_operand (TREE_TYPE (TREE_TYPE (fndecl)), dest, src);
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (! integer_all_onesp (size))
+    {
+      tree src_len = c_strlen (src, 1);
+      if (src_len
+         && host_integerp (src_len, 1)
+         && host_integerp (len, 1)
+         && ! tree_int_cst_lt (len, src_len))
+       {
+         /* If LEN >= strlen (SRC), optimize into __strcat_chk.  */
+         fn = built_in_decls[BUILT_IN_STRCAT_CHK];
+         if (!fn)
+           return 0;
+
+         arglist = build_tree_list (NULL_TREE, size);
+         arglist = tree_cons (NULL_TREE, src, arglist);
+         arglist = tree_cons (NULL_TREE, dest, arglist);
+         return build_function_call_expr (fn, arglist);
+       }
+      return 0;
+    }
+
+  arglist = build_tree_list (NULL_TREE, len);
+  arglist = tree_cons (NULL_TREE, src, arglist);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_strncat_chk is used, assume strncat is available.  */
+  fn = built_in_decls[BUILT_IN_STRNCAT];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to __{,v}sprintf_chk with argument list ARGLIST.  Return 0 if
+   a normal call should be emitted rather than expanding the function
+   inline.  FCODE is either BUILT_IN_SPRINTF_CHK or BUILT_IN_VSPRINTF_CHK.  */
+
+static tree
+fold_builtin_sprintf_chk (tree arglist, enum built_in_function fcode)
+{
+  tree dest, size, len, fn, fmt, flag;
+  const char *fmt_str;
+
+  /* Verify the required arguments in the original call.  */
+  if (! arglist)
+    return 0;
+  dest = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (dest)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  flag = TREE_VALUE (arglist);
+  if (TREE_CODE (TREE_TYPE (flag)) != INTEGER_TYPE)
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  size = TREE_VALUE (arglist);
+  if (TREE_CODE (TREE_TYPE (size)) != INTEGER_TYPE)
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  fmt = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (fmt)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  len = NULL_TREE;
+
+  /* Check whether the format is a literal string constant.  */
+  fmt_str = c_getstr (fmt);
+  if (fmt_str != NULL)
+    {
+      /* If the format doesn't contain % args or %%, we know the size.  */
+      if (strchr (fmt_str, '%') == 0)
+       {
+         if (fcode != BUILT_IN_SPRINTF_CHK || arglist == NULL_TREE)
+           len = build_int_cstu (size_type_node, strlen (fmt_str));
+       }
+      /* If the format is "%s" and first ... argument is a string literal,
+        we know the size too.  */
+      else if (fcode == BUILT_IN_SPRINTF_CHK && strcmp (fmt_str, "%s") == 0)
+       {
+         tree arg;
+
+         if (arglist && !TREE_CHAIN (arglist))
+           {
+             arg = TREE_VALUE (arglist);
+             if (POINTER_TYPE_P (TREE_TYPE (arg)))
+               {
+                 len = c_strlen (arg, 1);
+                 if (! len || ! host_integerp (len, 1))
+                   len = NULL_TREE;
+               }
+           }
+       }
+    }
+
+  if (! integer_all_onesp (size))
+    {
+      if (! len || ! tree_int_cst_lt (len, size))
+       return 0;
+    }
+
+  /* Only convert __{,v}sprintf_chk to {,v}sprintf if flag is 0
+     or if format doesn't contain % chars or is "%s".  */
+  if (! integer_zerop (flag))
+    {
+      if (fmt_str == NULL)
+       return 0;
+      if (strchr (fmt_str, '%') != NULL && strcmp (fmt_str, "%s"))
+       return 0;
+    }
+
+  arglist = tree_cons (NULL_TREE, fmt, arglist);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_{,v}sprintf_chk is used, assume {,v}sprintf is available.  */
+  fn = built_in_decls[fcode == BUILT_IN_VSPRINTF_CHK
+                     ? BUILT_IN_VSPRINTF : BUILT_IN_SPRINTF];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to {,v}snprintf with argument list ARGLIST.  Return 0 if
+   a normal call should be emitted rather than expanding the function
+   inline.  FCODE is either BUILT_IN_SNPRINTF_CHK or
+   BUILT_IN_VSNPRINTF_CHK.  If MAXLEN is not NULL, it is maximum length
+   passed as second argument.  */
+
+tree
+fold_builtin_snprintf_chk (tree arglist, tree maxlen,
+                          enum built_in_function fcode)
+{
+  tree dest, size, len, fn, fmt, flag;
+  const char *fmt_str;
+
+  /* Verify the required arguments in the original call.  */
+  if (! arglist)
+    return 0;
+  dest = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (dest)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  len = TREE_VALUE (arglist);
+  if (TREE_CODE (TREE_TYPE (len)) != INTEGER_TYPE)
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  flag = TREE_VALUE (arglist);
+  if (TREE_CODE (TREE_TYPE (len)) != INTEGER_TYPE)
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  size = TREE_VALUE (arglist);
+  if (TREE_CODE (TREE_TYPE (size)) != INTEGER_TYPE)
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+  if (! arglist)
+    return 0;
+  fmt = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (fmt)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+
+  if (! host_integerp (size, 1))
+    return 0;
+
+  if (! integer_all_onesp (size))
+    {
+      if (! host_integerp (len, 1))
+       {
+         /* If LEN is not constant, try MAXLEN too.
+            For MAXLEN only allow optimizing into non-_ocs function
+            if SIZE is >= MAXLEN, never convert to __ocs_fail ().  */
+         if (maxlen == NULL_TREE || ! host_integerp (maxlen, 1))
+           return 0;
+         len = maxlen;
+       }
+
+      if (tree_int_cst_lt (size, len))
+       return 0;
+    }
+
+  /* Only convert __{,v}snprintf_chk to {,v}snprintf if flag is 0
+     or if format doesn't contain % chars or is "%s".  */
+  if (! integer_zerop (flag))
+    {
+      fmt_str = c_getstr (fmt);
+      if (fmt_str == NULL)
+       return 0;
+      if (strchr (fmt_str, '%') != NULL && strcmp (fmt_str, "%s"))
+       return 0;
+    }
+
+  arglist = tree_cons (NULL_TREE, fmt, arglist);
+  arglist = tree_cons (NULL_TREE, len, arglist);
+  arglist = tree_cons (NULL_TREE, dest, arglist);
+
+  /* If __builtin_{,v}snprintf_chk is used, assume {,v}snprintf is
+     available.  */
+  fn = built_in_decls[fcode == BUILT_IN_VSNPRINTF_CHK
+                     ? BUILT_IN_VSNPRINTF : BUILT_IN_SNPRINTF];
+  if (!fn)
+    return 0;
+
+  return build_function_call_expr (fn, arglist);
+}
+
+/* Fold a call to the {,v}printf{,_unlocked} and __{,v}printf_chk builtins.
+
+   Return 0 if no simplification was possible, otherwise return the
+   simplified form of the call as a tree.  FCODE is the BUILT_IN_*
+   code of the function to be simplified.  */
+
+static tree
+fold_builtin_printf (tree fndecl, tree arglist, bool ignore,
+                    enum built_in_function fcode)
+{
+  tree fmt, fn = NULL_TREE, fn_putchar, fn_puts, arg, call;
+  const char *fmt_str = NULL;
+
+  /* If the return value is used, don't do the transformation.  */
+  if (! ignore)
+    return 0;
+
+  /* Verify the required arguments in the original call.  */
+  if (fcode == BUILT_IN_PRINTF_CHK || fcode == BUILT_IN_VPRINTF_CHK)
+    {
+      tree flag;
+
+      if (! arglist)
+       return 0;
+      flag = TREE_VALUE (arglist);
+      if (TREE_CODE (TREE_TYPE (flag)) != INTEGER_TYPE
+         || TREE_SIDE_EFFECTS (flag))
+       return 0;
+      arglist = TREE_CHAIN (arglist);
+    }
+
+  if (! arglist)
+    return 0;
+  fmt = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (fmt)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+
+  /* Check whether the format is a literal string constant.  */
+  fmt_str = c_getstr (fmt);
+  if (fmt_str == NULL)
+    return NULL_TREE;
+
+  if (fcode == BUILT_IN_PRINTF_UNLOCKED)
+    {
+      fn_putchar = implicit_built_in_decls[BUILT_IN_PUTCHAR_UNLOCKED];
+      fn_puts = implicit_built_in_decls[BUILT_IN_PUTS_UNLOCKED];
+    }
+  else
+    {
+      fn_putchar = implicit_built_in_decls[BUILT_IN_PUTCHAR];
+      fn_puts = implicit_built_in_decls[BUILT_IN_PUTS];
+    }
+
+  if (strcmp (fmt_str, "%s") == 0 || strchr (fmt_str, '%') == NULL)
+    {
+      const char *str;
+
+      if (strcmp (fmt_str, "%s") == 0)
+       {
+         if (fcode == BUILT_IN_VPRINTF || fcode == BUILT_IN_VPRINTF_CHK)
+           return 0;
+
+         if (! arglist
+             || ! POINTER_TYPE_P (TREE_TYPE (TREE_VALUE (arglist)))
+             || TREE_CHAIN (arglist))
+           return 0;
+
+         str = c_getstr (TREE_VALUE (arglist));
+         if (str == NULL)
+           return 0;
+       }
+      else
+       {
+         /* The format specifier doesn't contain any '%' characters.  */
+         if (fcode != BUILT_IN_VPRINTF && fcode != BUILT_IN_VPRINTF_CHK
+             && arglist)
+           return 0;
+         str = fmt_str;
+       }
+
+      /* If the string was "", printf does nothing.  */
+      if (str[0] == '\0')
+       return build_int_cst (TREE_TYPE (TREE_TYPE (fndecl)), 0);
+
+      /* If the string has length of 1, call putchar.  */
+      if (str[1] == '\0')
+       {
+         /* Given printf("c"), (where c is any one character,)
+            convert "c"[0] to an int and pass that to the replacement
+            function.  */
+         arg = build_int_cst (NULL_TREE, str[0]);
+         arglist = build_tree_list (NULL_TREE, arg);
+         fn = fn_putchar;
+       }
+      else
+       {
+         /* If the string was "string\n", call puts("string").  */
+         size_t len = strlen (str);
+         if (str[len - 1] == '\n')
+           {
+             /* Create a NUL-terminated string that's one char shorter
+                than the original, stripping off the trailing '\n'.  */
+             char *newstr = alloca (len);
+             memcpy (newstr, str, len - 1);
+             newstr[len - 1] = 0;
+
+             arg = build_string_literal (len, newstr);
+             arglist = build_tree_list (NULL_TREE, arg);
+             fn = fn_puts;
+           }
+         else
+           /* We'd like to arrange to call fputs(string,stdout) here,
+              but we need stdout and don't have a way to get it yet.  */
+           return 0;
+       }
+    }
+
+  /* The other optimizations can be done only on the non-va_list variants.  */
+  else if (fcode == BUILT_IN_VPRINTF || fcode == BUILT_IN_VPRINTF_CHK)
+    return 0;
+
+  /* If the format specifier was "%s\n", call __builtin_puts(arg).  */
+  else if (strcmp (fmt_str, "%s\n") == 0)
+    {
+      if (! arglist
+         || ! POINTER_TYPE_P (TREE_TYPE (TREE_VALUE (arglist)))
+         || TREE_CHAIN (arglist))
+       return 0;
+      fn = fn_puts;
+    }
+
+  /* If the format specifier was "%c", call __builtin_putchar(arg).  */
+  else if (strcmp (fmt_str, "%c") == 0)
+    {
+      if (! arglist
+         || TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != INTEGER_TYPE
+         || TREE_CHAIN (arglist))
+       return 0;
+      fn = fn_putchar;
+    }
+
+  if (!fn)
+    return 0;
+
+  call = build_function_call_expr (fn, arglist);
+  return fold_convert (TREE_TYPE (TREE_TYPE (fndecl)), call);
+}
+
+/* Fold a call to the {,v}fprintf{,_unlocked} and __{,v}printf_chk builtins.
+
+   Return 0 if no simplification was possible, otherwise return the
+   simplified form of the call as a tree.  FCODE is the BUILT_IN_*
+   code of the function to be simplified.  */
+
+static tree
+fold_builtin_fprintf (tree fndecl, tree arglist, bool ignore,
+                     enum built_in_function fcode)
+{
+  tree fp, fmt, fn = NULL_TREE, fn_fputc, fn_fputs, arg, call;
+  const char *fmt_str = NULL;
+
+  /* If the return value is used, don't do the transformation.  */
+  if (! ignore)
+    return 0;
+
+  /* Verify the required arguments in the original call.  */
+  if (! arglist)
+    return 0;
+  fp = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (fp)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+
+  if (fcode == BUILT_IN_FPRINTF_CHK || fcode == BUILT_IN_VFPRINTF_CHK)
+    {
+      tree flag;
+
+      if (! arglist)
+       return 0;
+      flag = TREE_VALUE (arglist);
+      if (TREE_CODE (TREE_TYPE (flag)) != INTEGER_TYPE
+         || TREE_SIDE_EFFECTS (flag))
+       return 0;
+      arglist = TREE_CHAIN (arglist);
+    }
+
+  if (! arglist)
+    return 0;
+  fmt = TREE_VALUE (arglist);
+  if (! POINTER_TYPE_P (TREE_TYPE (fmt)))
+    return 0;
+  arglist = TREE_CHAIN (arglist);
+
+  /* Check whether the format is a literal string constant.  */
+  fmt_str = c_getstr (fmt);
+  if (fmt_str == NULL)
+    return NULL_TREE;
+
+  if (fcode == BUILT_IN_FPRINTF_UNLOCKED)
+    {
+      fn_fputc = implicit_built_in_decls[BUILT_IN_FPUTC_UNLOCKED];
+      fn_fputs = implicit_built_in_decls[BUILT_IN_FPUTS_UNLOCKED];
+    }
+  else
+    {
+      fn_fputc = implicit_built_in_decls[BUILT_IN_FPUTC];
+      fn_fputs = implicit_built_in_decls[BUILT_IN_FPUTS];
+    }
+
+  /* If the format doesn't contain % args or %%, use strcpy.  */
+  if (strchr (fmt_str, '%') == NULL)
+    {
+      if (fcode != BUILT_IN_VFPRINTF && fcode != BUILT_IN_VFPRINTF_CHK
+         && arglist)
+       return 0;
+
+      /* If the format specifier was "", fprintf does nothing.  */
+      if (fmt_str[0] == '\0')
+       {
+         /* If FP has side-effects, just wait until gimplification is
+            done.  */
+         if (TREE_SIDE_EFFECTS (fp))
+           return 0;
+
+         return build_int_cst (TREE_TYPE (TREE_TYPE (fndecl)), 0);
+       }
+
+      /* When "string" doesn't contain %, replace all cases of
+        fprintf (fp, string) with fputs (string, fp).  The fputs
+        builtin will take care of special cases like length == 1.  */
+      arglist = build_tree_list (NULL_TREE, fp);
+      arglist = tree_cons (NULL_TREE, fmt, arglist);
+      fn = fn_fputs;
+    }
+
+  /* The other optimizations can be done only on the non-va_list variants.  */
+  else if (fcode == BUILT_IN_VFPRINTF || fcode == BUILT_IN_VFPRINTF_CHK)
+    return 0;
+
+  /* If the format specifier was "%s", call __builtin_fputs (arg, fp).  */
+  else if (strcmp (fmt_str, "%s") == 0)
+    {
+      if (! arglist
+         || ! POINTER_TYPE_P (TREE_TYPE (TREE_VALUE (arglist)))
+         || TREE_CHAIN (arglist))
+       return 0;
+      arg = TREE_VALUE (arglist);
+      arglist = build_tree_list (NULL_TREE, fp);
+      arglist = tree_cons (NULL_TREE, arg, arglist);
+      fn = fn_fputs;
+    }
+
+  /* If the format specifier was "%c", call __builtin_fputc (arg, fp).  */
+  else if (strcmp (fmt_str, "%c") == 0)
+    {
+      if (! arglist
+         || TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != INTEGER_TYPE
+         || TREE_CHAIN (arglist))
+       return 0;
+      arg = TREE_VALUE (arglist);
+      arglist = build_tree_list (NULL_TREE, fp);
+      arglist = tree_cons (NULL_TREE, arg, arglist);
+      fn = fn_fputc;
+    }
+
+  if (!fn)
+    return 0;
+
+  call = build_function_call_expr (fn, arglist);
+  return fold_convert (TREE_TYPE (TREE_TYPE (fndecl)), call);
+}