install.texi (Installation): Remove obsolete description for libstdc++ which is now...
[platform/upstream/gcc.git] / gcc / c-common.c
index 2f25a67..e07a7d3 100644 (file)
@@ -22,8 +22,6 @@ Boston, MA 02111-1307, USA.  */
 #include "config.h"
 #include "system.h"
 #include "tree.h"
-#include "c-lex.h"
-#include "c-tree.h"
 #include "flags.h"
 #include "toplev.h"
 #include "output.h"
@@ -31,18 +29,38 @@ Boston, MA 02111-1307, USA.  */
 #include "rtl.h"
 #include "ggc.h"
 #include "expr.h"
+#include "c-common.h"
+#include "defaults.h"
 #include "tm_p.h"
-
-#if USE_CPPLIB
+#include "intl.h"
+#include "diagnostic.h"
+#include "obstack.h"
 #include "cpplib.h"
 cpp_reader  parse_in;
-cpp_options parse_options;
-enum cpp_token cpp_token;
-#endif
 
 #undef WCHAR_TYPE_SIZE
 #define WCHAR_TYPE_SIZE TYPE_PRECISION (wchar_type_node)
 
+#ifndef WINT_TYPE
+#define WINT_TYPE "unsigned int"
+#endif
+
+#ifndef INTMAX_TYPE
+#define INTMAX_TYPE ((INT_TYPE_SIZE == LONG_LONG_TYPE_SIZE)    \
+                    ? "int"                                    \
+                    : ((LONG_TYPE_SIZE == LONG_LONG_TYPE_SIZE) \
+                       ? "long int"                            \
+                       : "long long int"))
+#endif
+
+#ifndef UINTMAX_TYPE
+#define UINTMAX_TYPE ((INT_TYPE_SIZE == LONG_LONG_TYPE_SIZE)   \
+                    ? "unsigned int"                           \
+                    : ((LONG_TYPE_SIZE == LONG_LONG_TYPE_SIZE) \
+                       ? "long unsigned int"                   \
+                       : "long long unsigned int"))
+#endif
+
 /* The following symbols are subsumed in the c_global_trees array, and
    listed here individually for documentation purposes.
 
@@ -131,12 +149,68 @@ enum cpp_token cpp_token;
 
        tree void_list_node;
 
+  The identifiers __FUNCTION__, __PRETTY_FUNCTION__, and __func__.
+
+       tree function_id_node;
+       tree pretty_function_id_node;
+       tree func_id_node;
+
 */
 
 tree c_global_trees[CTI_MAX];
 
+/* Nonzero means don't recognize the non-ANSI builtin functions.  */
+
+int flag_no_builtin;
+
+/* Nonzero means don't recognize the non-ANSI builtin functions.
+   -ansi sets this.  */
+
+int flag_no_nonansi_builtin;
+
+/* If non-NULL, dump the tree structure for the entire translation
+   unit to this file.  */
+
+const char *flag_dump_translation_unit;
+
+/* Warn about *printf or *scanf format/argument anomalies. */
+
+int warn_format;
+
+/* Warn about Y2K problems with strftime formats.  */
+
+int warn_format_y2k;
+
+/* Warn about excess arguments to formats.  */
+
+int warn_format_extra_args;
+
+/* Warn about non-literal format arguments.  */
+
+int warn_format_nonliteral;
+
+/* Nonzero means warn about possible violations of sequence point rules.  */
+
+int warn_sequence_point;
+
+/* The elements of `ridpointers' are identifier nodes for the reserved
+   type names and storage classes.  It is indexed by a RID_... value.  */
+tree *ridpointers;
+
 tree (*make_fname_decl)                PARAMS ((tree, const char *, int));
 
+/* If non-NULL, the address of a language-specific function that
+   returns 1 for language-specific statement codes.  */
+int (*lang_statement_code_p)           PARAMS ((enum tree_code));
+
+/* If non-NULL, the address of a language-specific function that takes
+   any action required right before expand_function_end is called.  */
+void (*lang_expand_function_end)       PARAMS ((void));
+
+/* If this variable is defined to a non-NULL value, it will be called
+   after the file has been completely parsed.  */
+void (*back_end_hook) PARAMS ((tree));
+
 /* Nonzero means the expression being parsed will never be evaluated.
    This is a count, since unevaluated expressions can nest.  */
 int skip_evaluation;
@@ -169,8 +243,8 @@ typedef struct
   int line;
   const char *file;
   int needs_warning;
+  tree if_stmt;
 } if_elt;
-static void tfaff                      PARAMS ((void));
 
 static if_elt *if_stack;
 
@@ -180,15 +254,16 @@ static int if_stack_space = 0;
 /* Stack pointer.  */
 static int if_stack_pointer = 0;
 
-/* Generate RTL for the start of an if-then, and record the start of it
+/* Record the start of an if-then, and record the start of it
    for ambiguous else detection.  */
 
 void
-c_expand_start_cond (cond, exitflag, compstmt_count)
+c_expand_start_cond (cond, compstmt_count)
      tree cond;
-     int exitflag;
      int compstmt_count;
 {
+  tree if_stmt;
+
   /* Make sure there is enough space on the stack.  */
   if (if_stack_space == 0)
     {
@@ -201,17 +276,29 @@ c_expand_start_cond (cond, exitflag, compstmt_count)
       if_stack = (if_elt *)xrealloc (if_stack, if_stack_space * sizeof (if_elt));
     }
 
+  if_stmt = build_stmt (IF_STMT, NULL_TREE, NULL_TREE, NULL_TREE);
+  IF_COND (if_stmt) = cond;
+  add_stmt (if_stmt);
+
   /* Record this if statement.  */
   if_stack[if_stack_pointer].compstmt_count = compstmt_count;
   if_stack[if_stack_pointer].file = input_filename;
   if_stack[if_stack_pointer].line = lineno;
   if_stack[if_stack_pointer].needs_warning = 0;
+  if_stack[if_stack_pointer].if_stmt = if_stmt;
   if_stack_pointer++;
+}
+
+/* Called after the then-clause for an if-statement is processed.  */
 
-  expand_start_cond (cond, exitflag);
+void
+c_finish_then ()
+{
+  tree if_stmt = if_stack[if_stack_pointer - 1].if_stmt;
+  RECHAIN_STMTS (if_stmt, THEN_CLAUSE (if_stmt));
 }
 
-/* Generate RTL for the end of an if-then.  Optionally warn if a nested
+/* Record the end of an if-then.  Optionally warn if a nested
    if statement had an ambiguous else clause.  */
 
 void
@@ -222,10 +309,10 @@ c_expand_end_cond ()
     warning_with_file_and_line (if_stack[if_stack_pointer].file,
                                if_stack[if_stack_pointer].line,
                                "suggest explicit braces to avoid ambiguous `else'");
-  expand_end_cond ();
+  last_expr_type = NULL_TREE;
 }
 
-/* Generate RTL between the then-clause and the else-clause
+/* Called between the then-clause and the else-clause
    of an if-then-else.  */
 
 void
@@ -244,8 +331,15 @@ c_expand_start_else ()
      case.  Also don't warn for any if statements nested in this else.  */
   if_stack[if_stack_pointer - 1].needs_warning = 0;
   if_stack[if_stack_pointer - 1].compstmt_count--;
+}
+
+/* Called after the else-clause for an if-statement is processed.  */
 
-  expand_start_else ();
+void
+c_finish_else ()
+{
+  tree if_stmt = if_stack[if_stack_pointer - 1].if_stmt;
+  RECHAIN_STMTS (if_stmt, ELSE_CLAUSE (if_stmt));
 }
 
 /* Make bindings for __FUNCTION__, __PRETTY_FUNCTION__, and __func__.  */
@@ -268,13 +362,14 @@ declare_function_name ()
       else
        name = "";
       printable_name = (*decl_printable_name) (current_function_decl, 2);
+
+      /* ISO C99 defines __func__, which is a variable, not a string
+        constant, and which is not a defined symbol at file scope.  */
+      (*make_fname_decl) (func_id_node, name, 0);
     }
   
-  (*make_fname_decl) (get_identifier ("__FUNCTION__"), name, 0);
-  (*make_fname_decl) (get_identifier ("__PRETTY_FUNCTION__"), printable_name, 1);
-  /* The ISO C people "of course" couldn't use __FUNCTION__ in the
-     ISO C 99 standard; instead a new variable is invented.  */
-  (*make_fname_decl) (get_identifier ("__func__"), name, 0);
+  (*make_fname_decl) (function_id_node, name, 0);
+  (*make_fname_decl) (pretty_function_id_node, printable_name, 1);
 }
 
 /* Given a chain of STRING_CST nodes,
@@ -291,6 +386,7 @@ combine_strings (strings)
   int wide_flag = 0;
   int wchar_bytes = TYPE_PRECISION (wchar_type_node) / BITS_PER_UNIT;
   int nchars;
+  const int nchars_max = flag_isoc99 ? 4095 : 509;
 
   if (TREE_CHAIN (strings))
     {
@@ -316,7 +412,7 @@ combine_strings (strings)
       if (wide_flag)
        length = length * wchar_bytes + wide_length;
 
-      p = ggc_alloc_string (NULL, length);
+      p = alloca (length);
 
       /* Copy the individual strings into the new combined string.
         If the combined string is wide, convert the chars to ints
@@ -355,9 +451,7 @@ combine_strings (strings)
       else
        *q = 0;
 
-      value = make_node (STRING_CST);
-      TREE_STRING_POINTER (value) = p;
-      TREE_STRING_LENGTH (value) = length;
+      value = build_string (length, p);
     }
   else
     {
@@ -370,6 +464,10 @@ combine_strings (strings)
   /* Compute the number of elements, for the array type.  */
   nchars = wide_flag ? length / wchar_bytes : length;
 
+  if (pedantic && nchars > nchars_max && c_language == clk_c)
+    pedwarn ("string length `%d' is greater than the minimum length `%d' ISO C%d is required to support",
+            nchars, nchars_max, flag_isoc99 ? 99 : 89);
+
   /* Create the array type for the string constant.
      -Wwrite-strings says make the string constant an array of const char
      so that copying it to a non-const pointer will get a warning.
@@ -718,6 +816,12 @@ decl_attributes (node, attributes, prefix_attributes)
                error ("no data type for mode `%s'", p);
              else
                {
+                 if (TYPE_PRECISION (typefm) > (TREE_UNSIGNED (type)
+                                                ? TYPE_PRECISION(uintmax_type_node)
+                                                : TYPE_PRECISION(intmax_type_node))
+                     && pedantic)
+                   pedwarn ("type with more precision than %s",
+                            TREE_UNSIGNED (type) ? "uintmax_t" : "intmax_t");
                  TREE_TYPE (decl) = type = typefm;
                  DECL_SIZE (decl) = DECL_SIZE_UNIT (decl) = 0;
                  layout_decl (decl, 0);
@@ -780,6 +884,20 @@ decl_attributes (node, attributes, prefix_attributes)
              error ("requested alignment is too large");
            else if (is_type)
              {
+               /* If we have a TYPE_DECL, then copy the type, so that we
+                  don't accidentally modify a builtin type.  See pushdecl.  */
+               if (decl && TREE_TYPE (decl) != error_mark_node
+                   && DECL_ORIGINAL_TYPE (decl) == NULL_TREE)
+                 {
+                   tree tt = TREE_TYPE (decl);
+                   DECL_ORIGINAL_TYPE (decl) = tt;
+                   tt = build_type_copy (tt);
+                   TYPE_NAME (tt) = decl;
+                   TREE_USED (tt) = TREE_USED (decl);
+                   TREE_TYPE (decl) = tt;
+                   type = tt;
+                 }
+
                TYPE_ALIGN (type) = (1 << i) * BITS_PER_UNIT;
                TYPE_USER_ALIGN (type) = 1;
              }
@@ -899,6 +1017,12 @@ decl_attributes (node, attributes, prefix_attributes)
                  }
              }
 
+           if (format_type == strftime_format_type && first_arg_num != 0)
+             {
+               error ("strftime formats cannot format arguments");
+               continue;
+             }
+
            record_function_format (DECL_NAME (decl),
                                    DECL_ASSEMBLER_NAME (decl),
                                    format_type, format_num, first_arg_num);
@@ -1162,105 +1286,499 @@ strip_attrs (specs_attrs)
 /* Check a printf/fprintf/sprintf/scanf/fscanf/sscanf format against
    a parameter list.  */
 
+/* The meaningfully distinct length modifiers for format checking recognised
+   by GCC.  */
+enum format_lengths
+{
+  FMT_LEN_none,
+  FMT_LEN_hh,
+  FMT_LEN_h,
+  FMT_LEN_l,
+  FMT_LEN_ll,
+  FMT_LEN_L,
+  FMT_LEN_z,
+  FMT_LEN_t,
+  FMT_LEN_j,
+  FMT_LEN_MAX
+};
+
+
+/* The standard versions in which various format features appeared.  */
+enum format_std_version
+{
+  STD_C89,
+  STD_C94,
+  STD_C99,
+  STD_EXT
+};
+
+/* The C standard version C++ is treated as equivalent to
+   or inheriting from, for the purpose of format features supported.  */
+#define CPLUSPLUS_STD_VER      STD_C89
+/* The C standard version we are checking formats against when pedantic.  */
+#define C_STD_VER              (c_language == clk_cplusplus              \
+                                ? CPLUSPLUS_STD_VER                      \
+                                : (flag_isoc99                           \
+                                   ? STD_C99                             \
+                                   : (flag_isoc94 ? STD_C94 : STD_C89)))
+/* The name to give to the standard version we are warning about when
+   pedantic.  FEATURE_VER is the version in which the feature warned out
+   appeared, which is higher than C_STD_VER.  */
+#define C_STD_NAME(FEATURE_VER) (c_language == clk_cplusplus   \
+                                ? "ISO C++"                    \
+                                : ((FEATURE_VER) == STD_EXT    \
+                                   ? "ISO C"                   \
+                                   : "ISO C89"))
+
+/* Flags that may apply to a particular kind of format checked by GCC.  */
+enum
+{
+  /* This format converts arguments of types determined by the
+     format string.  */
+  FMT_FLAG_ARG_CONVERT = 1,
+  /* The scanf allocation 'a' kludge applies to this format kind.  */
+  FMT_FLAG_SCANF_A_KLUDGE = 2,
+  /* A % during parsing a specifier is allowed to be a modified % rather
+     that indicating the format is broken and we are out-of-sync.  */
+  FMT_FLAG_FANCY_PERCENT_OK = 4,
+  /* With $ operand numbers, it is OK to reference the same argument more
+     than once.  */
+  FMT_FLAG_DOLLAR_MULTIPLE = 8
+  /* Not included here: details of whether width or precision may occur
+     (controlled by width_char and precision_char); details of whether
+     '*' can be used for these (width_type and precision_type); details
+     of whether length modifiers can occur (length_char_specs); details
+     of when $ operand numbers are allowed (always, for the formats
+     supported, if arguments are converted).  */
+};
+
+
+/* Structure describing a length modifier supported in format checking, and
+   possibly a doubled version such as "hh".  */
+typedef struct
+{
+  /* Name of the single-character length modifier.  */
+  const char *name;
+  /* Index into a format_char_info.types array.  */
+  enum format_lengths index;
+  /* Standard version this length appears in.  */
+  enum format_std_version std;
+  /* Same, if the modifier can be repeated, or NULL if it can't.  */
+  const char *double_name;
+  enum format_lengths double_index;
+  enum format_std_version double_std;
+} format_length_info;
+
+
+/* Structure desribing the combination of a conversion specifier
+   (or a set of specifiers which act identically) and a length modifier.  */
+typedef struct
+{
+  /* The standard version this combination of length and type appeared in.
+     This is only relevant if greater than those for length and type
+     individually; otherwise it is ignored.  */
+  enum format_std_version std;
+  /* The name to use for the type, if different from that generated internally
+     (e.g., "signed size_t").  */
+  const char *name;
+  /* The type itself.  */
+  tree *type;
+} format_type_detail;
+
+
+/* Macros to fill out tables of these.  */
+#define BADLEN { 0, NULL, NULL }
+#define NOLENGTHS      { BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+
+
+/* Structure desribing a format conversion specifier (or a set of specifiers
+   which act identically), and the length modifiers used with it.  */
+typedef struct
+{
+  const char *format_chars;
+  int pointer_count;
+  enum format_std_version std;
+  /* Types accepted for each length modifier.  */
+  format_type_detail types[FMT_LEN_MAX];
+  /* List of other modifier characters allowed with these specifiers.
+     This lists flags, and additionally "w" for width, "p" for precision,
+     "a" for scanf "a" allocation extension (not applicable in C99 mode),
+     "*" for scanf suppression, and "E" and "O" for those strftime
+     modifiers.  */
+  const char *flag_chars;
+  /* List of additional flags describing these conversion specifiers.
+     "c" for generic character pointers being allowed, "2" for strftime
+     two digit year formats, "3" for strftime formats giving two digit
+     years in some locales, "4" for "2" which becomes "3" with an "E" modifier,
+     "o" if use of strftime "O" is a GNU extension beyond C99,
+     "W" if the argument is a pointer which is dereferenced and written into,
+     "i" for printf integer formats where the '0' flag is ignored with
+     precision, and "[" for the starting character of a scanf scanset.  */
+  const char *flags2;
+} format_char_info;
+
+
+/* Structure describing a flag accepted by some kind of format.  */
+typedef struct
+{
+  /* The flag character in question (0 for end of array).  */
+  int flag_char;
+  /* Zero if this entry describes the flag character in general, or a
+     non-zero character that may be found in flags2 if it describes the
+     flag when used with certain formats only.  If the latter, only
+     the first such entry found that applies to the current conversion
+     specifier is used; the values of `name' and `long_name' it supplies
+     will be used, if non-NULL and the standard version is higher than
+     the unpredicated one, for any pedantic warning.  For example, 'o'
+     for strftime formats (meaning 'O' is an extension over C99).  */
+  int predicate;
+  /* The name to use for this flag in diagnostic messages.  For example,
+     N_("`0' flag"), N_("field width").  */
+  const char *name;
+  /* Long name for this flag in diagnostic messages; currently only used for
+     "ISO C does not support ...".  For example, N_("the `I' printf flag").  */
+  const char *long_name;
+  /* The standard version in which it appeared.  */
+  enum format_std_version std;
+} format_flag_spec;
+
+
+/* Structure describing a combination of flags that is bad for some kind
+   of format.  */
+typedef struct
+{
+  /* The first flag character in question (0 for end of array).  */
+  int flag_char1;
+  /* The second flag character.  */
+  int flag_char2;
+  /* Non-zero if the message should say that the first flag is ignored with
+     the second, zero if the combination should simply be objected to.  */
+  int ignored;
+  /* Zero if this entry applies whenever this flag combination occurs,
+     a non-zero character from flags2 if it only applies in some
+     circumstances (e.g. 'i' for printf formats ignoring 0 with precision).  */
+  int predicate;
+} format_flag_pair;
+
+
+/* Structure describing a particular kind of format processed by GCC.  */
+typedef struct
+{
+  /* The name of this kind of format, for use in diagnostics.  */
+  const char *name;
+  /* Specifications of the length modifiers accepted; possibly NULL.  */
+  const format_length_info *length_char_specs;
+  /* Details of the conversion specification characters accepted.  */
+  const format_char_info *conversion_specs;
+  /* String listing the flag characters that are accepted.  */
+  const char *flag_chars;
+  /* String listing modifier characters (strftime) accepted.  May be NULL.  */
+  const char *modifier_chars;
+  /* Details of the flag characters, including pseudo-flags.  */
+  const format_flag_spec *flag_specs;
+  /* Details of bad combinations of flags.  */
+  const format_flag_pair *bad_flag_pairs;
+  /* Flags applicable to this kind of format.  */
+  int flags;
+  /* Flag character to treat a width as, or 0 if width not used.  */
+  int width_char;
+  /* Flag character to treat a precision as, or 0 if precision not used.  */
+  int precision_char;
+  /* If a flag character has the effect of suppressing the conversion of
+     an argument ('*' in scanf), that flag character, otherwise 0.  */
+  int suppression_char;
+  /* Flag character to treat a length modifier as (ignored if length
+     modifiers not used).  Need not be placed in flag_chars for conversion
+     specifiers, but is used to check for bad combinations such as length
+     modifier with assignment suppression in scanf.  */
+  int length_code_char;
+  /* Pointer to type of argument expected if '*' is used for a width,
+     or NULL if '*' not used for widths.  */
+  tree *width_type;
+  /* Pointer to type of argument expected if '*' is used for a precision,
+     or NULL if '*' not used for precisions.  */
+  tree *precision_type;
+} format_kind_info;
+
+
+/* Structure describing details of a type expected in format checking,
+   and the type to check against it.  */
+typedef struct format_wanted_type
+{
+  /* The type wanted.  */
+  tree wanted_type;
+  /* The name of this type to use in diagnostics.  */
+  const char *wanted_type_name;
+  /* The level of indirection through pointers at which this type occurs.  */
+  int pointer_count;
+  /* Whether, when pointer_count is 1, to allow any character type when
+     pedantic, rather than just the character or void type specified.  */
+  int char_lenient_flag;
+  /* Whether the argument, dereferenced once, is written into and so the
+     argument must not be a pointer to a const-qualified type.  */
+  int writing_in_flag;
+  /* If warnings should be of the form "field precision is not type int",
+     the name to use (in this case "field precision"), otherwise NULL,
+     for "%s format, %s arg" type messages.  If (in an extension), this
+     is a pointer type, wanted_type_name should be set to include the
+     terminating '*' characters of the type name to give a correct
+     message.  */
+  const char *name;
+  /* The actual parameter to check against the wanted type.  */
+  tree param;
+  /* The argument number of that parameter.  */
+  int arg_num;
+  /* The next type to check for this format conversion, or NULL if none.  */
+  struct format_wanted_type *next;
+} format_wanted_type;
+
+
+static const format_length_info printf_length_specs[] =
+{
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C99 },
+  { "q", FMT_LEN_ll, STD_EXT, NULL, 0, 0 },
+  { "L", FMT_LEN_L, STD_C89, NULL, 0, 0 },
+  { "z", FMT_LEN_z, STD_C99, NULL, 0, 0 },
+  { "Z", FMT_LEN_z, STD_EXT, NULL, 0, 0 },
+  { "t", FMT_LEN_t, STD_C99, NULL, 0, 0 },
+  { "j", FMT_LEN_j, STD_C99, NULL, 0, 0 },
+  { NULL, 0, 0, NULL, 0, 0 }
+};
+
+
+/* This differs from printf_length_specs only in that "Z" is not accepted.  */
+static const format_length_info scanf_length_specs[] =
+{
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C99 },
+  { "q", FMT_LEN_ll, STD_EXT, NULL, 0, 0 },
+  { "L", FMT_LEN_L, STD_C89, NULL, 0, 0 },
+  { "z", FMT_LEN_z, STD_C99, NULL, 0, 0 },
+  { "t", FMT_LEN_t, STD_C99, NULL, 0, 0 },
+  { "j", FMT_LEN_j, STD_C99, NULL, 0, 0 },
+  { NULL, 0, 0, NULL, 0, 0 }
+};
+
+
+static const format_flag_spec printf_flag_specs[] =
+{
+  { ' ',  0, N_("` ' flag"),        N_("the ` ' printf flag"),              STD_C89 },
+  { '+',  0, N_("`+' flag"),        N_("the `+' printf flag"),              STD_C89 },
+  { '#',  0, N_("`#' flag"),        N_("the `#' printf flag"),              STD_C89 },
+  { '0',  0, N_("`0' flag"),        N_("the `0' printf flag"),              STD_C89 },
+  { '-',  0, N_("`-' flag"),        N_("the `-' printf flag"),              STD_C89 },
+  { '\'', 0, N_("`'' flag"),        N_("the `'' printf flag"),              STD_EXT },
+  { 'I',  0, N_("`I' flag"),        N_("the `I' printf flag"),              STD_EXT },
+  { 'w',  0, N_("field width"),     N_("field width in printf format"),     STD_C89 },
+  { 'p',  0, N_("precision"),       N_("precision in printf format"),       STD_C89 },
+  { 'L',  0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+  { 0, 0, NULL, NULL, 0 }
+};
+
+
+static const format_flag_pair printf_flag_pairs[] =
+{
+  { ' ', '+', 1, 0   },
+  { '0', '-', 1, 0   },
+  { '0', 'p', 1, 'i' },
+  { 0, 0, 0, 0 }
+};
+
+
+static const format_flag_spec scanf_flag_specs[] =
+{
+  { '*',  0, N_("assignment suppression"), N_("assignment suppression"),          STD_C89 },
+  { 'a',  0, N_("`a' flag"),               N_("the `a' scanf flag"),              STD_EXT },
+  { 'w',  0, N_("field width"),            N_("field width in scanf format"),     STD_C89 },
+  { 'L',  0, N_("length modifier"),        N_("length modifier in scanf format"), STD_C89 },
+  { '\'', 0, N_("`'' flag"),               N_("the `'' scanf flag"),              STD_EXT },
+  { 'I',  0, N_("`I' flag"),               N_("the `I' scanf flag"),              STD_EXT },
+  { 0, 0, NULL, NULL, 0 }
+};
+
+
+static const format_flag_pair scanf_flag_pairs[] =
+{
+  { '*', 'L', 0, 0 },
+  { 0, 0, 0, 0 }
+};
+
+
+static const format_flag_spec strftime_flag_specs[] =
+{
+  { '_', 0,   N_("`_' flag"),     N_("the `_' strftime flag"),          STD_EXT },
+  { '-', 0,   N_("`-' flag"),     N_("the `-' strftime flag"),          STD_EXT },
+  { '0', 0,   N_("`0' flag"),     N_("the `0' strftime flag"),          STD_EXT },
+  { '^', 0,   N_("`^' flag"),     N_("the `^' strftime flag"),          STD_EXT },
+  { '#', 0,   N_("`#' flag"),     N_("the `#' strftime flag"),          STD_EXT },
+  { 'w', 0,   N_("field width"),  N_("field width in strftime format"), STD_EXT },
+  { 'E', 0,   N_("`E' modifier"), N_("the `E' strftime modifier"),      STD_C99 },
+  { 'O', 0,   N_("`O' modifier"), N_("the `O' strftime modifier"),      STD_C99 },
+  { 'O', 'o', NULL,               N_("the `O' modifier"),               STD_EXT },
+  { 0, 0, NULL, NULL, 0 }
+};
+
+
+static const format_flag_pair strftime_flag_pairs[] =
+{
+  { 'E', 'O', 0, 0 },
+  { '_', '-', 0, 0 },
+  { '_', '0', 0, 0 },
+  { '-', '0', 0, 0 },
+  { '^', '#', 0, 0 },
+  { 0, 0, 0, 0 }
+};
+
+
 #define T_I    &integer_type_node
+#define T89_I  { STD_C89, NULL, T_I }
+#define T99_I  { STD_C99, NULL, T_I }
 #define T_L    &long_integer_type_node
+#define T89_L  { STD_C89, NULL, T_L }
 #define T_LL   &long_long_integer_type_node
+#define T99_LL { STD_C99, NULL, T_LL }
+#define TEX_LL { STD_EXT, NULL, T_LL }
 #define T_S    &short_integer_type_node
+#define T89_S  { STD_C89, NULL, T_S }
 #define T_UI   &unsigned_type_node
+#define T89_UI { STD_C89, NULL, T_UI }
+#define T99_UI { STD_C99, NULL, T_UI }
 #define T_UL   &long_unsigned_type_node
+#define T89_UL { STD_C89, NULL, T_UL }
 #define T_ULL  &long_long_unsigned_type_node
+#define T99_ULL        { STD_C99, NULL, T_ULL }
+#define TEX_ULL        { STD_EXT, NULL, T_ULL }
 #define T_US   &short_unsigned_type_node
+#define T89_US { STD_C89, NULL, T_US }
 #define T_F    &float_type_node
+#define T89_F  { STD_C89, NULL, T_F }
+#define T99_F  { STD_C99, NULL, T_F }
 #define T_D    &double_type_node
+#define T89_D  { STD_C89, NULL, T_D }
+#define T99_D  { STD_C99, NULL, T_D }
 #define T_LD   &long_double_type_node
+#define T89_LD { STD_C89, NULL, T_LD }
+#define T99_LD { STD_C99, NULL, T_LD }
 #define T_C    &char_type_node
+#define T89_C  { STD_C89, NULL, T_C }
+#define T_SC   &signed_char_type_node
+#define T99_SC { STD_C99, NULL, T_SC }
 #define T_UC   &unsigned_char_type_node
+#define T99_UC { STD_C99, NULL, T_UC }
 #define T_V    &void_type_node
+#define T89_V  { STD_C89, NULL, T_V }
 #define T_W    &wchar_type_node
-#define T_ST    &sizetype
-
-typedef struct {
-  const char *format_chars;
-  int pointer_count;
-  /* Type of argument if no length modifier is used.  */
-  tree *nolen;
-  /* Type of argument if length modifier for shortening to byte is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *hhlen;
-  /* Type of argument if length modifier for shortening is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *hlen;
-  /* Type of argument if length modifier `l' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *llen;
-  /* Type of argument if length modifier `q' or `ll' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *qlen;
-  /* Type of argument if length modifier `L' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *bigllen;
-  /* Type of argument if length modifiers 'z' or `Z' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *zlen;
-  /* List of other modifier characters allowed with these options.  */
-  const char *flag_chars;
-} format_char_info;
+#define T94_W  { STD_C94, "wchar_t", T_W }
+#define TEX_W  { STD_EXT, "wchar_t", T_W }
+#define T_WI   &wint_type_node
+#define T94_WI { STD_C94, "wint_t", T_WI }
+#define TEX_WI { STD_EXT, "wint_t", T_WI }
+#define T_ST    &c_size_type_node
+#define T99_ST { STD_C99, "size_t", T_ST }
+#define T_SST   &signed_size_type_node
+#define T99_SST        { STD_C99, "signed size_t", T_SST }
+#define T_PD    &ptrdiff_type_node
+#define T99_PD { STD_C99, "ptrdiff_t", T_PD }
+#define T_UPD   &unsigned_ptrdiff_type_node
+#define T99_UPD        { STD_C99, "unsigned ptrdiff_t", T_UPD }
+#define T_IM    &intmax_type_node
+#define T99_IM { STD_C99, "intmax_t", T_IM }
+#define T_UIM   &uintmax_type_node
+#define T99_UIM        { STD_C99, "uintmax_t", T_UIM }
+
+static const format_char_info print_char_table[] =
+{
+  /* C89 conversion specifiers.  */
+  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM  }, "-wp0 +'I", "i" },
+  { "oxX", 0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "-wp0#",    "i" },
+  { "u",   0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "-wp0'I",   "i" },
+  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#'", ""  },
+  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#",  ""  },
+  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-w",       ""  },
+  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wp",      "c" },
+  { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-w",       "c" },
+  { "n",   1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM  }, "",         "W" },
+  /* C99 conversion specifiers.  */
+  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#'", ""  },
+  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#",  ""  },
+  /* X/Open conversion specifiers.  */
+  { "C",   0, STD_EXT, { TEX_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-w",       ""  },
+  { "S",   1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wp",      ""  },
+  /* GNU conversion specifiers.  */
+  { "m",   0, STD_EXT, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wp",      ""  },
+  { NULL,  0, 0, NOLENGTHS, NULL, NULL }
+};
 
-static format_char_info print_char_table[] = {
-  { "di",      0,      T_I,    T_I,    T_I,    T_L,    T_LL,   T_LL,   T_ST,   "-wp0 +"        },
-  { "oxX",     0,      T_UI,   T_UI,   T_UI,   T_UL,   T_ULL,  T_ULL,  T_ST,   "-wp0#"         },
-  { "u",       0,      T_UI,   T_UI,   T_UI,   T_UL,   T_ULL,  T_ULL,  T_ST,   "-wp0"          },
-/* A GNU extension.  */
-  { "m",       0,      T_V,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "-wp"           },
-  { "feEgGaA", 0,      T_D,    NULL,   NULL,   NULL,   NULL,   T_LD,   NULL,   "-wp0 +#"       },
-  { "c",       0,      T_I,    NULL,   NULL,   T_W,    NULL,   NULL,   NULL,   "-w"            },
-  { "C",       0,      T_W,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "-w"            },
-  { "s",       1,      T_C,    NULL,   NULL,   T_W,    NULL,   NULL,   NULL,   "-wp"           },
-  { "S",       1,      T_W,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "-wp"           },
-  { "p",       1,      T_V,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "-w"            },
-  { "n",       1,      T_I,    NULL,   T_S,    T_L,    T_LL,   NULL,   NULL,   ""              },
-  { NULL,      0,      NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL            }
+static const format_char_info scan_char_table[] =
+{
+  /* C89 conversion specifiers.  */
+  { "di",    1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM  }, "*w'I", "W"   },
+  { "u",     1, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "*w'I", "W"   },
+  { "oxX",   1, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "*w",   "W"   },
+  { "efgEG", 1, STD_C89, { T89_F,   BADLEN,  BADLEN,  T89_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "*w'",  "W"   },
+  { "c",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*w",   "cW"  },
+  { "s",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*aw",  "cW"  },
+  { "[",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*aw",  "cW[" },
+  { "p",     2, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*w",   "W"   },
+  { "n",     1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM  }, "",     "W"   },
+  /* C99 conversion specifiers.  */
+  { "FaA",   1, STD_C99, { T99_F,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "*w'",  "W"   },
+  /* X/Open conversion specifiers.  */
+  { "C",     1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*w",   "W"   },
+  { "S",     1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*aw",  "W"   },
+  { NULL, 0, 0, NOLENGTHS, NULL, NULL }
 };
 
-static format_char_info scan_char_table[] = {
-  { "di",      1,      T_I,    T_C,    T_S,    T_L,    T_LL,   T_LL,   NULL,   "*"     },
-  { "ouxX",    1,      T_UI,   T_UC,   T_US,   T_UL,   T_ULL,  T_ULL,  NULL,   "*"     },
-  { "efgEGaA", 1,      T_F,    NULL,   NULL,   T_D,    NULL,   T_LD,   NULL,   "*"     },
-  { "c",       1,      T_C,    NULL,   NULL,   T_W,    NULL,   NULL,   NULL,   "*"     },
-  { "s",       1,      T_C,    NULL,   NULL,   T_W,    NULL,   NULL,   NULL,   "*a"    },
-  { "[",       1,      T_C,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "*a"    },
-  { "C",       1,      T_W,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "*"     },
-  { "S",       1,      T_W,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "*a"    },
-  { "p",       2,      T_V,    NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   "*"     },
-  { "n",       1,      T_I,    T_C,    T_S,    T_L,    T_LL,   NULL,   NULL,   ""      },
-  { NULL,      0,      NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL    }
+static format_char_info time_char_table[] =
+{
+  /* C89 conversion specifiers.  */
+  { "ABZab",           0, STD_C89, NOLENGTHS, "^#",     ""   },
+  { "cx",              0, STD_C89, NOLENGTHS, "E",      "3"  },
+  { "HIMSUWdmw",       0, STD_C89, NOLENGTHS, "-_0Ow",  ""   },
+  { "j",               0, STD_C89, NOLENGTHS, "-_0Ow",  "o"  },
+  { "p",               0, STD_C89, NOLENGTHS, "#",      ""   },
+  { "X",               0, STD_C89, NOLENGTHS, "E",      ""   },
+  { "y",               0, STD_C89, NOLENGTHS, "EO-_0w", "4"  },
+  { "Y",               0, STD_C89, NOLENGTHS, "-_0EOw", "o"  },
+  { "%",               0, STD_C89, NOLENGTHS, "",       ""   },
+  /* C99 conversion specifiers.  */
+  { "C",               0, STD_C99, NOLENGTHS, "-_0EOw", "o"  },
+  { "D",               0, STD_C99, NOLENGTHS, "",       "2"  },
+  { "eVu",             0, STD_C99, NOLENGTHS, "-_0Ow",  ""   },
+  { "FRTnrt",          0, STD_C99, NOLENGTHS, "",       ""   },
+  { "g",               0, STD_C99, NOLENGTHS, "O-_0w",  "2o" },
+  { "G",               0, STD_C99, NOLENGTHS, "-_0Ow",  "o"  },
+  { "h",               0, STD_C99, NOLENGTHS, "^#",     ""   },
+  { "z",               0, STD_C99, NOLENGTHS, "O",      "o"  },
+  /* GNU conversion specifiers.  */
+  { "kls",             0, STD_EXT, NOLENGTHS, "-_0Ow",  ""   },
+  { "P",               0, STD_EXT, NOLENGTHS, "",       ""   },
+  { NULL,              0, 0, NOLENGTHS, NULL, NULL }
 };
 
-/* Handle format characters recognized by glibc's strftime.c.
-   '2' - MUST do years as only two digits
-   '3' - MAY do years as only two digits (depending on locale)
-   'E' - E modifier is acceptable
-   'O' - O modifier is acceptable to Standard C
-   'o' - O modifier is acceptable as a GNU extension
-   'G' - other GNU extensions  */
-
-static format_char_info time_char_table[] = {
-  { "y",               0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "2EO-_0w" },
-  { "D",               0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "2" },
-  { "g",               0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "2O-_0w" },
-  { "cx",              0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "3E" },
-  { "%RTXnrt",         0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "" },
-  { "P",               0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "G" },
-  { "HIMSUWdemw",      0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Ow" },
-  { "Vju",             0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Oow" },
-  { "Gklsz",           0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0OGw" },
-  { "ABZa",            0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "^#" },
-  { "p",               0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "#" },
-  { "bh",              0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "^" },
-  { "CY",              0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0EOw" },
-  { NULL,              0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+
+/* This must be in the same order as enum format_type.  */
+static const format_kind_info format_types[] =
+{
+  { "printf",   printf_length_specs, print_char_table, " +#0-'I", NULL, 
+    printf_flag_specs, printf_flag_pairs,
+    FMT_FLAG_ARG_CONVERT|FMT_FLAG_DOLLAR_MULTIPLE, 'w', 'p', 0, 'L',
+    &integer_type_node, &integer_type_node
+  },
+  { "scanf",    scanf_length_specs,  scan_char_table,  "*'I", NULL, 
+    scanf_flag_specs, scanf_flag_pairs,
+    FMT_FLAG_ARG_CONVERT|FMT_FLAG_SCANF_A_KLUDGE, 'w', 0, '*', 'L',
+    NULL, NULL
+  },
+  { "strftime", NULL,                time_char_table,  "_-0^#", "EO",
+    strftime_flag_specs, strftime_flag_pairs,
+    FMT_FLAG_FANCY_PERCENT_OK, 'w', 0, 0, 0,
+    NULL, NULL
+  }
 };
 
+
 typedef struct function_format_info
 {
   struct function_format_info *next;  /* next structure on the list */
@@ -1283,14 +1801,66 @@ typedef struct international_format_info
 
 static international_format_info *international_format_list = NULL;
 
-static void check_format_info  PARAMS ((function_format_info *, tree));
+/* Structure detailing the results of checking a format function call
+   where the format expression may be a conditional expression with
+   many leaves resulting from nested conditional expressions.  */
+typedef struct
+{
+  /* Number of leaves of the format argument that could not be checked
+     as they were not string literals.  */
+  int number_non_literal;
+  /* Number of leaves of the format argument that were null pointers or
+     string literals, but had extra format arguments.  */
+  int number_extra_args;
+  /* Number of leaves of the format argument that were null pointers or
+     string literals, but had extra format arguments and used $ operand
+     numbers.  */
+  int number_dollar_extra_args;
+  /* Number of leaves of the format argument that were wide string
+     literals.  */
+  int number_wide;
+  /* Number of leaves of the format argument that were empty strings.  */
+  int number_empty;
+  /* Number of leaves of the format argument that were unterminated
+     strings.  */
+  int number_unterminated;
+  /* Number of leaves of the format argument that were not counted above.  */
+  int number_other;
+} format_check_results;
+
+static void check_format_info  PARAMS ((int *, function_format_info *, tree));
+static void check_format_info_recurse PARAMS ((int *, format_check_results *,
+                                              function_format_info *, tree,
+                                              tree, int));
+static void check_format_info_main PARAMS ((int *, format_check_results *,
+                                           function_format_info *,
+                                           const char *, int, tree, int));
+static void status_warning PARAMS ((int *, const char *, ...))
+     ATTRIBUTE_PRINTF_2;
+
+static void init_dollar_format_checking                PARAMS ((int, tree));
+static int maybe_read_dollar_number            PARAMS ((int *, const char **, int,
+                                                        tree, tree *,
+                                                        const format_kind_info *));
+static void finish_dollar_format_checking      PARAMS ((int *, format_check_results *));
+
+static const format_flag_spec *get_flag_spec   PARAMS ((const format_flag_spec *,
+                                                        int, const char *));
+
+static void check_format_types PARAMS ((int *, format_wanted_type *));
+static int is_valid_printf_arglist PARAMS ((tree));
+static rtx c_expand_builtin PARAMS ((tree, rtx, enum machine_mode, enum expand_modifier));
+static rtx c_expand_builtin_printf PARAMS ((tree, rtx, enum machine_mode,
+                                           enum expand_modifier, int));
 
 /* Initialize the table of functions to perform format checking on.
-   The ANSI functions are always checked (whether <stdio.h> is
+   The ISO C functions are always checked (whether <stdio.h> is
    included or not), since it is common to call printf without
    including <stdio.h>.  There shouldn't be a problem with this,
-   since ANSI reserves these function names whether you include the
-   header file or not.  In any case, the checking is harmless.
+   since ISO C reserves these function names whether you include the
+   header file or not.  In any case, the checking is harmless.  With
+   -ffreestanding, these default attributes are disabled, and must be
+   specified manually if desired.
 
    Also initialize the name of function that modify the format string for
    internationalization purposes.  */
@@ -1298,30 +1868,55 @@ static void check_format_info   PARAMS ((function_format_info *, tree));
 void
 init_function_format_info ()
 {
-  record_function_format (get_identifier ("printf"), NULL_TREE,
-                         printf_format_type, 1, 2);
-  record_function_format (get_identifier ("fprintf"), NULL_TREE,
-                         printf_format_type, 2, 3);
-  record_function_format (get_identifier ("sprintf"), NULL_TREE,
-                         printf_format_type, 2, 3);
-  record_function_format (get_identifier ("scanf"), NULL_TREE,
-                         scanf_format_type, 1, 2);
-  record_function_format (get_identifier ("fscanf"), NULL_TREE,
-                         scanf_format_type, 2, 3);
-  record_function_format (get_identifier ("sscanf"), NULL_TREE,
-                         scanf_format_type, 2, 3);
-  record_function_format (get_identifier ("vprintf"), NULL_TREE,
-                         printf_format_type, 1, 0);
-  record_function_format (get_identifier ("vfprintf"), NULL_TREE,
-                         printf_format_type, 2, 0);
-  record_function_format (get_identifier ("vsprintf"), NULL_TREE,
-                         printf_format_type, 2, 0);
-  record_function_format (get_identifier ("strftime"), NULL_TREE,
-                         strftime_format_type, 3, 0);
-
-  record_international_format (get_identifier ("gettext"), NULL_TREE, 1);
-  record_international_format (get_identifier ("dgettext"), NULL_TREE, 2);
-  record_international_format (get_identifier ("dcgettext"), NULL_TREE, 2);
+  if (flag_hosted)
+    {
+      /* Functions from ISO/IEC 9899:1990.  */
+      record_function_format (get_identifier ("printf"), NULL_TREE,
+                             printf_format_type, 1, 2);
+      record_function_format (get_identifier ("__builtin_printf"), NULL_TREE,
+                             printf_format_type, 1, 2);
+      record_function_format (get_identifier ("fprintf"), NULL_TREE,
+                             printf_format_type, 2, 3);
+      record_function_format (get_identifier ("sprintf"), NULL_TREE,
+                             printf_format_type, 2, 3);
+      record_function_format (get_identifier ("scanf"), NULL_TREE,
+                             scanf_format_type, 1, 2);
+      record_function_format (get_identifier ("fscanf"), NULL_TREE,
+                             scanf_format_type, 2, 3);
+      record_function_format (get_identifier ("sscanf"), NULL_TREE,
+                             scanf_format_type, 2, 3);
+      record_function_format (get_identifier ("vprintf"), NULL_TREE,
+                             printf_format_type, 1, 0);
+      record_function_format (get_identifier ("vfprintf"), NULL_TREE,
+                             printf_format_type, 2, 0);
+      record_function_format (get_identifier ("vsprintf"), NULL_TREE,
+                             printf_format_type, 2, 0);
+      record_function_format (get_identifier ("strftime"), NULL_TREE,
+                             strftime_format_type, 3, 0);
+    }
+
+  if (flag_hosted && flag_isoc99)
+    {
+      /* ISO C99 adds the snprintf and vscanf family functions.  */
+      record_function_format (get_identifier ("snprintf"), NULL_TREE,
+                             printf_format_type, 3, 4);
+      record_function_format (get_identifier ("vsnprintf"), NULL_TREE,
+                             printf_format_type, 3, 0);
+      record_function_format (get_identifier ("vscanf"), NULL_TREE,
+                             scanf_format_type, 1, 0);
+      record_function_format (get_identifier ("vfscanf"), NULL_TREE,
+                             scanf_format_type, 2, 0);
+      record_function_format (get_identifier ("vsscanf"), NULL_TREE,
+                             scanf_format_type, 2, 0);
+    }
+
+  if (flag_hosted && flag_noniso_default_format_attributes)
+    {
+      /* Uniforum/GNU gettext functions, not in ISO C.  */
+      record_international_format (get_identifier ("gettext"), NULL_TREE, 1);
+      record_international_format (get_identifier ("dgettext"), NULL_TREE, 2);
+      record_international_format (get_identifier ("dcgettext"), NULL_TREE, 2);
+    }
 }
 
 /* Record information for argument format checking.  FUNCTION_IDENT is
@@ -1402,21 +1997,18 @@ record_international_format (name, assembler_name, format_num)
 
   info->format_num = format_num;
 }
-
-static void
-tfaff ()
-{
-  warning ("too few arguments for format");
-}
 \f
 /* Check the argument list of a call to printf, scanf, etc.
    NAME is the function identifier.
    ASSEMBLER_NAME is the function's assembler identifier.
    (Either NAME or ASSEMBLER_NAME, but not both, may be NULL_TREE.)
-   PARAMS is the list of argument values.  */
+   PARAMS is the list of argument values.  Also, if -Wmissing-format-attribute,
+   warn for calls to vprintf or vscanf in functions with no such format
+   attribute themselves.  */
 
 void
-check_function_format (name, assembler_name, params)
+check_function_format (status, name, assembler_name, params)
+     int *status;
      tree name;
      tree assembler_name;
      tree params;
@@ -1431,37 +2023,290 @@ check_function_format (name, assembler_name, params)
          : (info->name == name))
        {
          /* Yup; check it.  */
-         check_format_info (info, params);
+         check_format_info (status, info, params);
+         if (warn_missing_format_attribute && info->first_arg_num == 0
+             && (format_types[info->format_type].flags & FMT_FLAG_ARG_CONVERT))
+           {
+             function_format_info *info2;
+             for (info2 = function_format_list; info2; info2 = info2->next)
+               if ((info2->assembler_name
+                    ? (info2->assembler_name == DECL_ASSEMBLER_NAME (current_function_decl))
+                    : (info2->name == DECL_NAME (current_function_decl)))
+                   && info2->format_type == info->format_type)
+                 break;
+             if (info2 == NULL)
+               warning ("function might be possible candidate for `%s' format attribute",
+                        format_types[info->format_type].name);
+           }
          break;
        }
     }
 }
 
+/* This function replaces `warning' inside the printf format checking
+   functions.  If the `status' parameter is non-NULL, then it is
+   dereferenced and set to 1 whenever a warning is caught.  Otherwise
+   it warns as usual by replicating the innards of the warning
+   function from diagnostic.c.  */
+static void
+status_warning VPARAMS ((int *status, const char *msgid, ...))
+{
+#ifndef ANSI_PROTOTYPES
+  int *status;
+  const char *msgid;
+#endif
+  va_list ap;
+  diagnostic_context dc;
+
+  VA_START (ap, msgid);
+
+#ifndef ANSI_PROTOTYPES
+  status = va_arg (ap, int *);
+  msgid = va_arg (ap, const char *);
+#endif
+
+  if (status)
+    *status = 1;
+  else
+    {
+      /* This duplicates the warning function behavior.  */
+      set_diagnostic_context
+       (&dc, msgid, &ap, input_filename, lineno, /* warn = */ 1);
+      report_diagnostic (&dc);
+    }
+
+  va_end (ap);
+}
+
+/* Variables used by the checking of $ operand number formats.  */
+static char *dollar_arguments_used = NULL;
+static int dollar_arguments_alloc = 0;
+static int dollar_arguments_count;
+static int dollar_first_arg_num;
+static int dollar_max_arg_used;
+static int dollar_format_warned;
+
+/* Initialize the checking for a format string that may contain $
+   parameter number specifications; we will need to keep track of whether
+   each parameter has been used.  FIRST_ARG_NUM is the number of the first
+   argument that is a parameter to the format, or 0 for a vprintf-style
+   function; PARAMS is the list of arguments starting at this argument.  */
+
+static void
+init_dollar_format_checking (first_arg_num, params)
+     int first_arg_num;
+     tree params;
+{
+  dollar_first_arg_num = first_arg_num;
+  dollar_arguments_count = 0;
+  dollar_max_arg_used = 0;
+  dollar_format_warned = 0;
+  if (first_arg_num > 0)
+    {
+      while (params)
+       {
+         dollar_arguments_count++;
+         params = TREE_CHAIN (params);
+       }
+    }
+  if (dollar_arguments_alloc < dollar_arguments_count)
+    {
+      if (dollar_arguments_used)
+       free (dollar_arguments_used);
+      dollar_arguments_alloc = dollar_arguments_count;
+      dollar_arguments_used = xmalloc (dollar_arguments_alloc);
+    }
+  if (dollar_arguments_alloc)
+    memset (dollar_arguments_used, 0, dollar_arguments_alloc);
+}
+
+
+/* Look for a decimal number followed by a $ in *FORMAT.  If DOLLAR_NEEDED
+   is set, it is an error if one is not found; otherwise, it is OK.  If
+   such a number is found, check whether it is within range and mark that
+   numbered operand as being used for later checking.  Returns the operand
+   number if found and within range, zero if no such number was found and
+   this is OK, or -1 on error.  PARAMS points to the first operand of the
+   format; PARAM_PTR is made to point to the parameter referred to.  If
+   a $ format is found, *FORMAT is updated to point just after it.  */
+
+static int
+maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
+                         fki)
+     int *status;
+     const char **format;
+     int dollar_needed;
+     tree params;
+     tree *param_ptr;
+     const format_kind_info *fki;
+{
+  int argnum;
+  int overflow_flag;
+  const char *fcp = *format;
+  if (*fcp < '0' || *fcp > '9')
+    {
+      if (dollar_needed)
+       {
+         status_warning (status, "missing $ operand number in format");
+         return -1;
+       }
+      else
+       return 0;
+    }
+  argnum = 0;
+  overflow_flag = 0;
+  while (*fcp >= '0' && *fcp <= '9')
+    {
+      int nargnum;
+      nargnum = 10 * argnum + (*fcp - '0');
+      if (nargnum < 0 || nargnum / 10 != argnum)
+       overflow_flag = 1;
+      argnum = nargnum;
+      fcp++;
+    }
+  if (*fcp != '$')
+    {
+      if (dollar_needed)
+       {
+         status_warning (status, "missing $ operand number in format");
+         return -1;
+       }
+      else
+       return 0;
+    }
+  *format = fcp + 1;
+  if (pedantic && !dollar_format_warned)
+    {
+      status_warning (status,
+                     "%s does not support %%n$ operand number formats",
+                     C_STD_NAME (STD_EXT));
+      dollar_format_warned = 1;
+    }
+  if (overflow_flag || argnum == 0
+      || (dollar_first_arg_num && argnum > dollar_arguments_count))
+    {
+      status_warning (status, "operand number out of range in format");
+      return -1;
+    }
+  if (argnum > dollar_max_arg_used)
+    dollar_max_arg_used = argnum;
+  /* For vprintf-style functions we may need to allocate more memory to
+     track which arguments are used.  */
+  while (dollar_arguments_alloc < dollar_max_arg_used)
+    {
+      int nalloc;
+      nalloc = 2 * dollar_arguments_alloc + 16;
+      dollar_arguments_used = xrealloc (dollar_arguments_used, nalloc);
+      memset (dollar_arguments_used + dollar_arguments_alloc, 0,
+             nalloc - dollar_arguments_alloc);
+      dollar_arguments_alloc = nalloc;
+    }
+  if (!(fki->flags & FMT_FLAG_DOLLAR_MULTIPLE)
+      && dollar_arguments_used[argnum - 1] == 1)
+    {
+      dollar_arguments_used[argnum - 1] = 2;
+      status_warning (status,
+                     "format argument %d used more than once in %s format",
+                     argnum, fki->name);
+    }
+  else
+    dollar_arguments_used[argnum - 1] = 1;
+  if (dollar_first_arg_num)
+    {
+      int i;
+      *param_ptr = params;
+      for (i = 1; i < argnum && *param_ptr != 0; i++)
+       *param_ptr = TREE_CHAIN (*param_ptr);
+
+      if (*param_ptr == 0)
+       {
+         /* This case shouldn't be caught here.  */
+         abort ();
+       }
+    }
+  else
+    *param_ptr = 0;
+  return argnum;
+}
+
+
+/* Finish the checking for a format string that used $ operand number formats
+   instead of non-$ formats.  We check for unused operands before used ones
+   (a serious error, since the implementation of the format function
+   can't know what types to pass to va_arg to find the later arguments).
+   and for unused operands at the end of the format (if we know how many
+   arguments the format had, so not for vprintf).  If there were operand
+   numbers out of range on a non-vprintf-style format, we won't have reached
+   here.  */
+
+static void
+finish_dollar_format_checking (status, res)
+     int *status;
+     format_check_results *res;
+{
+  int i;
+  for (i = 0; i < dollar_max_arg_used; i++)
+    {
+      if (!dollar_arguments_used[i])
+       status_warning (status, "format argument %d unused before used argument %d in $-style format",
+                i + 1, dollar_max_arg_used);
+    }
+  if (dollar_first_arg_num && dollar_max_arg_used < dollar_arguments_count)
+    {
+      res->number_other--;
+      res->number_dollar_extra_args++;
+    }
+}
+
+
+/* Retrieve the specification for a format flag.  SPEC contains the
+   specifications for format flags for the applicable kind of format.
+   FLAG is the flag in question.  If PREDICATES is NULL, the basic
+   spec for that flag must be retrieved and this function aborts if
+   it cannot be found.  If PREDICATES is not NULL, it is a string listing
+   possible predicates for the spec entry; if an entry predicated on any
+   of these is found, it is returned, otherwise NULL is returned.  */
+
+static const format_flag_spec *
+get_flag_spec (spec, flag, predicates)
+     const format_flag_spec *spec;
+     int flag;
+     const char *predicates;
+{
+  int i;
+  for (i = 0; spec[i].flag_char != 0; i++)
+    {
+      if (spec[i].flag_char != flag)
+       continue;
+      if (predicates != NULL)
+       {
+         if (spec[i].predicate != 0
+             && strchr (predicates, spec[i].predicate) != 0)
+           return &spec[i];
+       }
+      else if (spec[i].predicate == 0)
+       return &spec[i];
+    }
+  if (predicates == NULL)
+    abort ();
+  else
+    return NULL;
+}
+
+
 /* Check the argument list of a call to printf, scanf, etc.
    INFO points to the function_format_info structure.
    PARAMS is the list of argument values.  */
 
 static void
-check_format_info (info, params)
+check_format_info (status, info, params)
+     int *status;
      function_format_info *info;
      tree params;
 {
-  int i;
   int arg_num;
-  int suppressed, wide, precise;
-  int length_char = 0;
-  int format_char;
-  int format_length;
   tree format_tree;
-  tree cur_param;
-  tree cur_type;
-  tree wanted_type;
-  tree first_fillin_param;
-  const char *format_chars;
-  format_char_info *fci = NULL;
-  char flag_chars[8];
-  int has_operand_number = 0;
-
+  format_check_results res;
   /* Skip to format argument.  If the argument isn't available, there's
      no work for us to do; prototype checking will catch the problem.  */
   for (arg_num = 1; ; ++arg_num)
@@ -1477,25 +2322,93 @@ check_format_info (info, params)
   if (format_tree == 0)
     return;
 
-  /* We can only check the format if it's a string constant.  */
-  while (TREE_CODE (format_tree) == NOP_EXPR)
-    format_tree = TREE_OPERAND (format_tree, 0); /* strip coercion */
+  res.number_non_literal = 0;
+  res.number_extra_args = 0;
+  res.number_dollar_extra_args = 0;
+  res.number_wide = 0;
+  res.number_empty = 0;
+  res.number_unterminated = 0;
+  res.number_other = 0;
 
-  if (TREE_CODE (format_tree) == CALL_EXPR
-      && TREE_CODE (TREE_OPERAND (format_tree, 0)) == ADDR_EXPR
-      && (TREE_CODE (TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0))
-         == FUNCTION_DECL))
+  check_format_info_recurse (status, &res, info, format_tree, params, arg_num);
+
+  if (res.number_non_literal > 0)
     {
-      tree function = TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0);
+      /* Functions taking a va_list normally pass a non-literal format
+        string.  These functions typically are declared with
+        first_arg_num == 0, so avoid warning in those cases.  */
+      if (info->first_arg_num != 0 && warn_format_nonliteral)
+       status_warning (status, "format not a string literal, argument types not checked");
+    }
 
-      /* See if this is a call to a known internationalization function
-        that modifies the format arg.  */
-      international_format_info *info;
+  /* If there were extra arguments to the format, normally warn.  However,
+     the standard does say extra arguments are ignored, so in the specific
+     case where we have multiple leaves (conditional expressions or
+     ngettext) allow extra arguments if at least one leaf didn't have extra
+     arguments, but was otherwise OK (either non-literal or checked OK).
+     If the format is an empty string, this should be counted similarly to the
+     case of extra format arguments.  */
+  if (res.number_extra_args > 0 && res.number_non_literal == 0
+      && res.number_other == 0 && warn_format_extra_args)
+    status_warning (status, "too many arguments for format");
+  if (res.number_dollar_extra_args > 0 && res.number_non_literal == 0
+      && res.number_other == 0 && warn_format_extra_args)
+    status_warning (status, "unused arguments in $-style format");
+  if (res.number_empty > 0 && res.number_non_literal == 0
+      && res.number_other == 0)
+    status_warning (status, "zero-length format string");
+
+  if (res.number_wide > 0)
+    status_warning (status, "format is a wide character string");
+
+  if (res.number_unterminated > 0)
+    status_warning (status, "unterminated format string");
+}
+
+
+/* Recursively check a call to a format function.  FORMAT_TREE is the
+   format parameter, which may be a conditional expression in which
+   both halves should be checked.  ARG_NUM is the number of the
+   format argument; PARAMS points just after it in the argument list.  */
+
+static void
+check_format_info_recurse (status, res, info, format_tree, params, arg_num)
+     int *status;
+     format_check_results *res;
+     function_format_info *info;
+     tree format_tree;
+     tree params;
+     int arg_num;
+{
+  int format_length;
+  const char *format_chars;
+  tree array_size = 0;
+  tree array_init;
+
+  if (TREE_CODE (format_tree) == NOP_EXPR)
+    {
+      /* Strip coercion.  */
+      check_format_info_recurse (status, res, info,
+                                TREE_OPERAND (format_tree, 0), params,
+                                arg_num);
+      return;
+    }
+
+  if (TREE_CODE (format_tree) == CALL_EXPR
+      && TREE_CODE (TREE_OPERAND (format_tree, 0)) == ADDR_EXPR
+      && (TREE_CODE (TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0))
+         == FUNCTION_DECL))
+    {
+      tree function = TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0);
 
-      for (info = international_format_list; info; info = info->next)
-       if (info->assembler_name
-           ? (info->assembler_name == DECL_ASSEMBLER_NAME (function))
-           : (info->name == DECL_NAME (function)))
+      /* See if this is a call to a known internationalization function
+        that modifies the format arg.  */
+      international_format_info *iinfo;
+
+      for (iinfo = international_format_list; iinfo; iinfo = iinfo->next)
+       if (iinfo->assembler_name
+           ? (iinfo->assembler_name == DECL_ASSEMBLER_NAME (function))
+           : (iinfo->name == DECL_NAME (function)))
          {
            tree inner_args;
            int i;
@@ -1503,53 +2416,119 @@ check_format_info (info, params)
            for (inner_args = TREE_OPERAND (format_tree, 1), i = 1;
                 inner_args != 0;
                 inner_args = TREE_CHAIN (inner_args), i++)
-             if (i == info->format_num)
+             if (i == iinfo->format_num)
                {
-                 format_tree = TREE_VALUE (inner_args);
-
-                 while (TREE_CODE (format_tree) == NOP_EXPR)
-                   format_tree = TREE_OPERAND (format_tree, 0);
+                 /* FIXME: with Marc Espie's __attribute__((nonnull))
+                    patch in GCC, we will have chained attributes,
+                    and be able to handle functions like ngettext
+                    with multiple format_arg attributes properly.  */
+                 check_format_info_recurse (status, res, info,
+                                            TREE_VALUE (inner_args), params,
+                                            arg_num);
+                 return;
                }
          }
     }
 
+  if (TREE_CODE (format_tree) == COND_EXPR)
+    {
+      /* Check both halves of the conditional expression.  */
+      check_format_info_recurse (status, res, info,
+                                TREE_OPERAND (format_tree, 1), params,
+                                arg_num);
+      check_format_info_recurse (status, res, info,
+                                TREE_OPERAND (format_tree, 2), params,
+                                arg_num);
+      return;
+    }
+
   if (integer_zerop (format_tree))
     {
-      warning ("null format string");
+      /* FIXME: this warning should go away once Marc Espie's
+        __attribute__((nonnull)) patch is in.  Instead, checking for
+        nonnull attributes should probably change this function to act
+        specially if info == NULL and add a res->number_null entry for
+        that case, or maybe add a function pointer to be called at
+        the end instead of hardcoding check_format_info_main.  */
+      status_warning (status, "null format string");
+
+      /* Skip to first argument to check, so we can see if this format
+        has any arguments (it shouldn't).  */
+      while (arg_num + 1 < info->first_arg_num)
+       {
+         if (params == 0)
+           return;
+         params = TREE_CHAIN (params);
+         ++arg_num;
+       }
+
+      if (params == 0)
+       res->number_other++;
+      else
+       res->number_extra_args++;
+
       return;
     }
+
   if (TREE_CODE (format_tree) != ADDR_EXPR)
     {
-      /* The user may get multiple warnings if the supplied argument
-        isn't even a string pointer.  */
-      /* Functions taking a va_list normally pass a non-literal format
-        string.  These functions typically are declared with
-        first_arg_num == 0, so avoid warning in those cases.  */
-      if (info->first_arg_num != 0 && warn_format > 1)
-       warning ("format not a string literal, argument types not checked");
+      res->number_non_literal++;
       return;
     }
   format_tree = TREE_OPERAND (format_tree, 0);
+  if (TREE_CODE (format_tree) == VAR_DECL
+      && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE
+      && (array_init = decl_constant_value (format_tree)) != format_tree
+      && TREE_CODE (array_init) == STRING_CST)
+    {
+      /* Extract the string constant initializer.  Note that this may include
+        a trailing NUL character that is not in the array (e.g.
+        const char a[3] = "foo";).  */
+      array_size = DECL_SIZE_UNIT (format_tree);
+      format_tree = array_init;
+    }
   if (TREE_CODE (format_tree) != STRING_CST)
     {
-      /* The user may get multiple warnings if the supplied argument
-        isn't even a string pointer.  */
-      /* Functions taking a va_list normally pass a non-literal format
-        string.  These functions typically are declared with
-        first_arg_num == 0, so avoid warning in those cases.  */
-      if (info->first_arg_num != 0 && warn_format > 1)
-       warning ("format not a string literal, argument types not checked");
+      res->number_non_literal++;
+      return;
+    }
+  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree))) != char_type_node)
+    {
+      res->number_wide++;
       return;
     }
   format_chars = TREE_STRING_POINTER (format_tree);
   format_length = TREE_STRING_LENGTH (format_tree);
-  if (format_length <= 1)
-    warning ("zero-length format string");
+  if (array_size != 0)
+    {
+      /* Variable length arrays can't be initialized.  */
+      if (TREE_CODE (array_size) != INTEGER_CST)
+       abort ();
+      if (host_integerp (array_size, 0))
+       {
+         HOST_WIDE_INT array_size_value = TREE_INT_CST_LOW (array_size);
+         if (array_size_value > 0
+             && array_size_value == (int) array_size_value
+             && format_length > array_size_value)
+           format_length = array_size_value;
+       }
+    }
+  if (format_length < 1)
+    {
+      res->number_unterminated++;
+      return;
+    }
+  if (format_length == 1)
+    {
+      res->number_empty++;
+      return;
+    }
   if (format_chars[--format_length] != 0)
     {
-      warning ("unterminated format string");
+      res->number_unterminated++;
       return;
     }
+
   /* Skip to first argument to check.  */
   while (arg_num + 1 < info->first_arg_num)
     {
@@ -1558,24 +2537,88 @@ check_format_info (info, params)
       params = TREE_CHAIN (params);
       ++arg_num;
     }
+  /* Provisionally increment res->number_other; check_format_info_main
+     will decrement it if it finds there are extra arguments, but this way
+     need not adjust it for every return.  */
+  res->number_other++;
+  check_format_info_main (status, res, info, format_chars, format_length,
+                         params, arg_num);
+}
+
+
+/* Do the main part of checking a call to a format function.  FORMAT_CHARS
+   is the NUL-terminated format string (which at this point may contain
+   internal NUL characters); FORMAT_LENGTH is its length (excluding the
+   terminating NUL character).  ARG_NUM is one less than the number of
+   the first format argument to check; PARAMS points to that format
+   argument in the list of arguments.  */
+
+static void
+check_format_info_main (status, res, info, format_chars, format_length,
+                       params, arg_num)
+     int *status;
+     format_check_results *res;
+     function_format_info *info;
+     const char *format_chars;
+     int format_length;
+     tree params;
+     int arg_num;
+{
+  const char *orig_format_chars = format_chars;
+  tree first_fillin_param = params;
+
+  const format_kind_info *fki = &format_types[info->format_type];
+  const format_flag_spec *flag_specs = fki->flag_specs;
+  const format_flag_pair *bad_flag_pairs = fki->bad_flag_pairs;
+
+  /* -1 if no conversions taking an operand have been found; 0 if one has
+     and it didn't use $; 1 if $ formats are in use.  */
+  int has_operand_number = -1;
+
+  init_dollar_format_checking (info->first_arg_num, first_fillin_param);
 
-  first_fillin_param = params;
   while (1)
     {
-      int aflag;
+      int i;
+      int suppressed = FALSE;
+      const char *length_chars = NULL;
+      enum format_lengths length_chars_val = FMT_LEN_none;
+      enum format_std_version length_chars_std = STD_C89;
+      int format_char;
+      tree cur_param;
+      tree wanted_type;
+      int main_arg_num = 0;
+      tree main_arg_params = 0;
+      enum format_std_version wanted_type_std;
+      const char *wanted_type_name;
+      format_wanted_type width_wanted_type;
+      format_wanted_type precision_wanted_type;
+      format_wanted_type main_wanted_type;
+      format_wanted_type *first_wanted_type = NULL;
+      format_wanted_type *last_wanted_type = NULL;
+      const format_length_info *fli = NULL;
+      const format_char_info *fci = NULL;
+      char flag_chars[256];
+      int aflag = 0;
       if (*format_chars == 0)
        {
-         if (format_chars - TREE_STRING_POINTER (format_tree) != format_length)
-           warning ("embedded `\\0' in format");
-         if (info->first_arg_num != 0 && params != 0 && ! has_operand_number)
-           warning ("too many arguments for format");
+         if (format_chars - orig_format_chars != format_length)
+           status_warning (status, "embedded `\\0' in format");
+         if (info->first_arg_num != 0 && params != 0
+             && has_operand_number <= 0)
+           {
+             res->number_other--;
+             res->number_extra_args++;
+           }
+         if (has_operand_number > 0)
+           finish_dollar_format_checking (status, res);
          return;
        }
       if (*format_chars++ != '%')
        continue;
       if (*format_chars == 0)
        {
-         warning ("spurious trailing `%%' in format");
+         status_warning (status, "spurious trailing `%%' in format");
          continue;
        }
       if (*format_chars == '%')
@@ -1584,295 +2627,406 @@ check_format_info (info, params)
          continue;
        }
       flag_chars[0] = 0;
-      suppressed = wide = precise = FALSE;
-      if (info->format_type == scanf_format_type)
+
+      if ((fki->flags & FMT_FLAG_ARG_CONVERT) && has_operand_number != 0)
        {
-         suppressed = *format_chars == '*';
-         if (suppressed)
-           ++format_chars;
-         while (ISDIGIT (*format_chars))
-           ++format_chars;
-       }
-      else if (info->format_type == strftime_format_type)
-        {
-         while (*format_chars != 0 && index ("_-0^#", *format_chars) != 0)
+         /* Possibly read a $ operand number at the start of the format.
+            If one was previously used, one is required here.  If one
+            is not used here, we can't immediately conclude this is a
+            format without them, since it could be printf %m or scanf %*.  */
+         int opnum;
+         opnum = maybe_read_dollar_number (status, &format_chars, 0,
+                                           first_fillin_param,
+                                           &main_arg_params, fki);
+         if (opnum == -1)
+           return;
+         else if (opnum > 0)
            {
-             if (pedantic)
-               warning ("ANSI C does not support the strftime `%c' flag",
-                        *format_chars);
-             if (index (flag_chars, *format_chars) != 0)
-               {
-                 warning ("repeated `%c' flag in format",
-                          *format_chars);
-                 ++format_chars;
-               }
-             else
-               {
-                 i = strlen (flag_chars);
-                 flag_chars[i++] = *format_chars++;
-                 flag_chars[i] = 0;
-               }
+             has_operand_number = 1;
+             main_arg_num = opnum + info->first_arg_num - 1;
            }
-         while (ISDIGIT ((unsigned char) *format_chars))
+       }
+
+      /* Read any format flags, but do not yet validate them beyond removing
+        duplicates, since in general validation depends on the rest of
+        the format.  */
+      while (*format_chars != 0
+            && strchr (fki->flag_chars, *format_chars) != 0)
+       {
+         if (strchr (flag_chars, *format_chars) != 0)
            {
-             wide = TRUE;
-              ++format_chars;
+             const format_flag_spec *s = get_flag_spec (flag_specs,
+                                                        *format_chars, NULL);
+             status_warning (status, "repeated %s in format", _(s->name));
            }
-         if (wide && pedantic)
-           warning ("ANSI C does not support strftime format width");
-         if (*format_chars == 'E' || *format_chars == 'O')
+         else
            {
              i = strlen (flag_chars);
-             flag_chars[i++] = *format_chars++;
+             flag_chars[i++] = *format_chars;
              flag_chars[i] = 0;
-             if (*format_chars == 'E' || *format_chars == 'O')
-               {
-                 warning ("multiple E/O modifiers in format");
-                 while (*format_chars == 'E' || *format_chars == 'O')
-                   ++format_chars;
-               }
            }
+         ++format_chars;
        }
-      else if (info->format_type == printf_format_type)
-       {
-         /* See if we have a number followed by a dollar sign.  If we do,
-            it is an operand number, so set PARAMS to that operand.  */
-         if (*format_chars >= '0' && *format_chars <= '9')
-           {
-             const char *p = format_chars;
-
-             while (*p >= '0' && *p++ <= '9')
-               ;
-
-             if (*p == '$')
-               {
-                 int opnum = atoi (format_chars);
 
-                 params = first_fillin_param;
-                 format_chars = p + 1;
-                 has_operand_number = 1;
-
-                 for (i = 1; i < opnum && params != 0; i++)
-                   params = TREE_CHAIN (params);
-
-                 if (opnum == 0 || params == 0)
-                   {
-                     warning ("operand number out of range in format");
-                     return;
-                   }
-               }
-           }
-
-         while (*format_chars != 0 && index (" +#0-", *format_chars) != 0)
-           {
-             if (index (flag_chars, *format_chars) != 0)
-               warning ("repeated `%c' flag in format", *format_chars++);
-             else
-               {
-                 i = strlen (flag_chars);
-                 flag_chars[i++] = *format_chars++;
-                 flag_chars[i] = 0;
-               }
-           }
-         /* "If the space and + flags both appear,
-            the space flag will be ignored."  */
-         if (index (flag_chars, ' ') != 0
-             && index (flag_chars, '+') != 0)
-           warning ("use of both ` ' and `+' flags in format");
-         /* "If the 0 and - flags both appear,
-            the 0 flag will be ignored."  */
-         if (index (flag_chars, '0') != 0
-             && index (flag_chars, '-') != 0)
-           warning ("use of both `0' and `-' flags in format");
-         if (*format_chars == '*')
+      /* Read any format width, possibly * or *m$.  */
+      if (fki->width_char != 0)
+       {
+         if (fki->width_type != NULL && *format_chars == '*')
            {
-             wide = TRUE;
+             i = strlen (flag_chars);
+             flag_chars[i++] = fki->width_char;
+             flag_chars[i] = 0;
              /* "...a field width...may be indicated by an asterisk.
                 In this case, an int argument supplies the field width..."  */
              ++format_chars;
              if (params == 0)
                {
-                 tfaff ();
+                 status_warning (status, "too few arguments for format");
                  return;
                }
+             if (has_operand_number != 0)
+               {
+                 int opnum;
+                 opnum = maybe_read_dollar_number (status, &format_chars,
+                                                   has_operand_number == 1,
+                                                   first_fillin_param,
+                                                   &params, fki);
+                 if (opnum == -1)
+                   return;
+                 else if (opnum > 0)
+                   {
+                     has_operand_number = 1;
+                     arg_num = opnum + info->first_arg_num - 1;
+                   }
+                 else
+                   has_operand_number = 0;
+               }
              if (info->first_arg_num != 0)
                {
                  cur_param = TREE_VALUE (params);
-                 params = TREE_CHAIN (params);
-                 ++arg_num;
-                 /* size_t is generally not valid here.
-                    It will work on most machines, because size_t and int
-                    have the same mode.  But might as well warn anyway,
-                    since it will fail on other machines.  */
-                 if ((TYPE_MAIN_VARIANT (TREE_TYPE (cur_param))
-                      != integer_type_node)
-                     &&
-                     (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param))
-                      != unsigned_type_node))
-                   warning ("field width is not type int (arg %d)", arg_num);
+                 if (has_operand_number <= 0)
+                   {
+                     params = TREE_CHAIN (params);
+                     ++arg_num;
+                   }
+                 width_wanted_type.wanted_type = *fki->width_type;
+                 width_wanted_type.wanted_type_name = NULL;
+                 width_wanted_type.pointer_count = 0;
+                 width_wanted_type.char_lenient_flag = 0;
+                 width_wanted_type.writing_in_flag = 0;
+                 width_wanted_type.name = _("field width");
+                 width_wanted_type.param = cur_param;
+                 width_wanted_type.arg_num = arg_num;
+                 width_wanted_type.next = NULL;
+                 if (last_wanted_type != 0)
+                   last_wanted_type->next = &width_wanted_type;
+                 if (first_wanted_type == 0)
+                   first_wanted_type = &width_wanted_type;
+                 last_wanted_type = &width_wanted_type;
                }
            }
          else
            {
+             /* Possibly read a numeric width.  If the width is zero,
+                we complain; for scanf this is bad according to the
+                standard, and for printf and strftime it cannot occur
+                because 0 is a flag.  */
+             int non_zero_width_char = FALSE;
+             int found_width = FALSE;
              while (ISDIGIT (*format_chars))
                {
-                 wide = TRUE;
+                 found_width = TRUE;
+                 if (*format_chars != '0')
+                   non_zero_width_char = TRUE;
                  ++format_chars;
                }
+             if (found_width && !non_zero_width_char)
+               status_warning (status, "zero width in %s format",
+                               fki->name);
+             if (found_width)
+               {
+                 i = strlen (flag_chars);
+                 flag_chars[i++] = fki->width_char;
+                 flag_chars[i] = 0;
+               }
            }
-         if (*format_chars == '.')
+       }
+
+      /* Read any format precision, possibly * or *m$.  */
+      if (fki->precision_char != 0 && *format_chars == '.')
+       {
+         ++format_chars;
+         i = strlen (flag_chars);
+         flag_chars[i++] = fki->precision_char;
+         flag_chars[i] = 0;
+         if (fki->precision_type != NULL && *format_chars == '*')
            {
-             precise = TRUE;
-             ++format_chars;
-             if (*format_chars != '*' && !ISDIGIT (*format_chars))
-               warning ("`.' not followed by `*' or digit in format");
              /* "...a...precision...may be indicated by an asterisk.
                 In this case, an int argument supplies the...precision."  */
-             if (*format_chars == '*')
+             ++format_chars;
+             if (has_operand_number != 0)
                {
-                 if (info->first_arg_num != 0)
+                 int opnum;
+                 opnum = maybe_read_dollar_number (status, &format_chars,
+                                                   has_operand_number == 1,
+                                                   first_fillin_param,
+                                                   &params, fki);
+                 if (opnum == -1)
+                   return;
+                 else if (opnum > 0)
                    {
-                     ++format_chars;
-                     if (params == 0)
-                       {
-                         tfaff ();
-                         return;
-                       }
-                     cur_param = TREE_VALUE (params);
-                     params = TREE_CHAIN (params);
-                     ++arg_num;
-                     if (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param))
-                         != integer_type_node)
-                       warning ("field width is not type int (arg %d)",
-                                arg_num);
+                     has_operand_number = 1;
+                     arg_num = opnum + info->first_arg_num - 1;
                    }
+                 else
+                   has_operand_number = 0;
                }
-             else
+             if (info->first_arg_num != 0)
                {
-                 while (ISDIGIT (*format_chars))
-                   ++format_chars;
+                 if (params == 0)
+                   {
+                     status_warning (status, "too few arguments for format");
+                     return;
+                   }
+                 cur_param = TREE_VALUE (params);
+                 if (has_operand_number <= 0)
+                   {
+                     params = TREE_CHAIN (params);
+                     ++arg_num;
+                   }
+                 precision_wanted_type.wanted_type = *fki->precision_type;
+                 precision_wanted_type.wanted_type_name = NULL;
+                 precision_wanted_type.pointer_count = 0;
+                 precision_wanted_type.char_lenient_flag = 0;
+                 precision_wanted_type.writing_in_flag = 0;
+                 precision_wanted_type.name = _("field precision");
+                 precision_wanted_type.param = cur_param;
+                 precision_wanted_type.arg_num = arg_num;
+                 precision_wanted_type.next = NULL;
+                 if (last_wanted_type != 0)
+                   last_wanted_type->next = &precision_wanted_type;
+                 if (first_wanted_type == 0)
+                   first_wanted_type = &precision_wanted_type;
+                 last_wanted_type = &precision_wanted_type;
                }
            }
+         else
+           {
+             while (ISDIGIT (*format_chars))
+               ++format_chars;
+           }
        }
 
-      aflag = 0;
-
-      if (info->format_type != strftime_format_type)
+      /* Read any length modifier, if this kind of format has them.  */
+      fli = fki->length_char_specs;
+      length_chars = NULL;
+      length_chars_val = FMT_LEN_none;
+      length_chars_std = STD_C89;
+      if (fli)
        {
-         if (*format_chars == 'h' || *format_chars == 'l')
-           length_char = *format_chars++;
-         else if (*format_chars == 'q' || *format_chars == 'L')
-           {
-             length_char = *format_chars++;
-             if (pedantic)
-               warning ("ANSI C does not support the `%c' length modifier",
-                        length_char);
-           }
-         else if (*format_chars == 'Z' || *format_chars == 'z')
+         while (fli->name != 0 && fli->name[0] != *format_chars)
+           fli++;
+         if (fli->name != 0)
            {
-             length_char = *format_chars++;
-             if (pedantic && (length_char == 'Z' || !flag_isoc99))
-               warning ("ANSI C does not support the `%c' length modifier",
-                        length_char);
+             format_chars++;
+             if (fli->double_name != 0 && fli->name[0] == *format_chars)
+               {
+                 format_chars++;
+                 length_chars = fli->double_name;
+                 length_chars_val = fli->double_index;
+                 length_chars_std = fli->double_std;
+               }
+             else
+               {
+                 length_chars = fli->name;
+                 length_chars_val = fli->index;
+                 length_chars_std = fli->std;
+               }
+             i = strlen (flag_chars);
+             flag_chars[i++] = fki->length_code_char;
+             flag_chars[i] = 0;
            }
-         else
-           length_char = 0;
-         if (length_char == 'l' && *format_chars == 'l')
+         if (pedantic)
            {
-             length_char = 'q', format_chars++;
-             if (pedantic && !flag_isoc99)
-               warning ("ANSI C does not support the `ll' length modifier");
+             /* Warn if the length modifier is non-standard.  */
+             if (length_chars_std > C_STD_VER)
+               status_warning (status, "%s does not support the `%s' %s length modifier",
+                               C_STD_NAME (length_chars_std), length_chars,
+                               fki->name);
            }
-         else if (length_char == 'h' && *format_chars == 'h')
+       }
+
+      /* Read any modifier (strftime E/O).  */
+      if (fki->modifier_chars != NULL)
+       {
+         while (*format_chars != 0
+                && strchr (fki->modifier_chars, *format_chars) != 0)
            {
-             length_char = 'H', format_chars++;
-             if (pedantic && !flag_isoc99)
-               warning ("ANSI C does not support the `hh' length modifier");
+             if (strchr (flag_chars, *format_chars) != 0)
+               {
+                 const format_flag_spec *s = get_flag_spec (flag_specs,
+                                                            *format_chars, NULL);
+                 status_warning (status, "repeated %s in format", _(s->name));
+               }
+             else
+               {
+                 i = strlen (flag_chars);
+                 flag_chars[i++] = *format_chars;
+                 flag_chars[i] = 0;
+               }
+             ++format_chars;
            }
-         if (*format_chars == 'a' && info->format_type == scanf_format_type)
+       }
+
+      /* Handle the scanf allocation kludge.  */
+      if (fki->flags & FMT_FLAG_SCANF_A_KLUDGE)
+       {
+         if (*format_chars == 'a' && !flag_isoc99)
            {
              if (format_chars[1] == 's' || format_chars[1] == 'S'
                  || format_chars[1] == '[')
                {
                  /* `a' is used as a flag.  */
-                 aflag = 1;
+                 i = strlen (flag_chars);
+                 flag_chars[i++] = 'a';
+                 flag_chars[i] = 0;
                  format_chars++;
                }
            }
-         if (suppressed && length_char != 0)
-           warning ("use of `*' and `%c' together in format", length_char);
        }
+
       format_char = *format_chars;
       if (format_char == 0
-         || (info->format_type != strftime_format_type && format_char == '%'))
+         || (!(fki->flags & FMT_FLAG_FANCY_PERCENT_OK) && format_char == '%'))
        {
-         warning ("conversion lacks type at end of format");
+         status_warning (status, "conversion lacks type at end of format");
          continue;
        }
-      /* The m, C, and S formats are GNU extensions.  */
-      if (pedantic && info->format_type != strftime_format_type
-         && (format_char == 'm' || format_char == 'C' || format_char == 'S'))
-       warning ("ANSI C does not support the `%c' format", format_char);
-      /* The a and A formats are C99 extensions.  */
-      if (pedantic && info->format_type != strftime_format_type
-         && (format_char == 'a' || format_char == 'A')
-         && !flag_isoc99)
-       warning ("ANSI C does not support the `%c' format", format_char);
       format_chars++;
-      switch (info->format_type)
-       {
-       case printf_format_type:
-         fci = print_char_table;
-         break;
-       case scanf_format_type:
-         fci = scan_char_table;
-         break;
-       case strftime_format_type:
-         fci = time_char_table;
-         break;
-       default:
-         abort ();
-       }
+      fci = fki->conversion_specs;
       while (fci->format_chars != 0
-            && index (fci->format_chars, format_char) == 0)
+            && strchr (fci->format_chars, format_char) == 0)
          ++fci;
       if (fci->format_chars == 0)
        {
           if (ISGRAPH(format_char))
-           warning ("unknown conversion type character `%c' in format",
+           status_warning (status, "unknown conversion type character `%c' in format",
                     format_char);
          else
-           warning ("unknown conversion type character 0x%x in format",
+           status_warning (status, "unknown conversion type character 0x%x in format",
                     format_char);
          continue;
        }
       if (pedantic)
        {
-         if (index (fci->flag_chars, 'G') != 0)
-           warning ("ANSI C does not support `%%%c'", format_char);
-         if (index (fci->flag_chars, 'o') != 0
-             && index (flag_chars, 'O') != 0)
-           warning ("ANSI C does not support `%%O%c'", format_char);
+         if (fci->std > C_STD_VER)
+           status_warning (status, "%s does not support the `%%%c' %s format",
+                           C_STD_NAME (fci->std), format_char, fki->name);
+       }
+
+      /* Validate the individual flags used, removing any that are invalid.  */
+      {
+       int d = 0;
+       for (i = 0; flag_chars[i] != 0; i++)
+         {
+           const format_flag_spec *s = get_flag_spec (flag_specs,
+                                                      flag_chars[i], NULL);
+           flag_chars[i - d] = flag_chars[i];
+           if (flag_chars[i] == fki->length_code_char)
+             continue;
+           if (strchr (fci->flag_chars, flag_chars[i]) == 0)
+             {
+               status_warning (status, "%s used with `%%%c' %s format",
+                               _(s->name), format_char, fki->name);
+               d++;
+               continue;
+             }
+           if (pedantic)
+             {
+               const format_flag_spec *t;
+               if (s->std > C_STD_VER)
+                 status_warning (status, "%s does not support %s",
+                                 C_STD_NAME (s->std), _(s->long_name));
+               t = get_flag_spec (flag_specs, flag_chars[i], fci->flags2);
+               if (t != NULL && t->std > s->std)
+                 {
+                   const char *long_name = (t->long_name != NULL
+                                            ? t->long_name
+                                            : s->long_name);
+                   if (t->std > C_STD_VER)
+                     status_warning (status, "%s does not support %s with the `%%%c' %s format",
+                                     C_STD_NAME (t->std), _(long_name),
+                                     format_char, fki->name);
+                 }
+             }
+         }
+       flag_chars[i - d] = 0;
+      }
+
+      if ((fki->flags & FMT_FLAG_SCANF_A_KLUDGE)
+         && strchr (flag_chars, 'a') != 0)
+       aflag = 1;
+
+      if (fki->suppression_char
+         && strchr (flag_chars, fki->suppression_char) != 0)
+       suppressed = 1;
+
+      /* Validate the pairs of flags used.  */
+      for (i = 0; bad_flag_pairs[i].flag_char1 != 0; i++)
+       {
+         const format_flag_spec *s, *t;
+         if (strchr (flag_chars, bad_flag_pairs[i].flag_char1) == 0)
+           continue;
+         if (strchr (flag_chars, bad_flag_pairs[i].flag_char2) == 0)
+           continue;
+         if (bad_flag_pairs[i].predicate != 0
+             && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0)
+           continue;
+         s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL);
+         t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL);
+         if (bad_flag_pairs[i].ignored)
+           {
+             if (bad_flag_pairs[i].predicate != 0)
+               status_warning (status, "%s ignored with %s and `%%%c' %s format",
+                               _(s->name), _(t->name), format_char,
+                               fki->name);
+             else
+               status_warning (status, "%s ignored with %s in %s format",
+                               _(s->name), _(t->name), fki->name);
+           }
+         else
+           {
+             if (bad_flag_pairs[i].predicate != 0)
+               status_warning (status, "use of %s and %s together with `%%%c' %s format",
+                               _(s->name), _(t->name), format_char,
+                               fki->name);
+             else
+               status_warning (status, "use of %s and %s together in %s format",
+                               _(s->name), _(t->name), fki->name);
+           }
        }
-      if (wide && index (fci->flag_chars, 'w') == 0)
-       warning ("width used with `%c' format", format_char);
-      if (index (fci->flag_chars, '2') != 0)
-       warning ("`%%%c' yields only last 2 digits of year", format_char);
-      else if (index (fci->flag_chars, '3') != 0)
-       warning ("`%%%c' yields only last 2 digits of year in some locales",
-                format_char);
-      if (precise && index (fci->flag_chars, 'p') == 0)
-       warning ("precision used with `%c' format", format_char);
-      if (aflag && index (fci->flag_chars, 'a') == 0)
+
+      /* Give Y2K warnings.  */
+      if (warn_format_y2k)
        {
-         warning ("`a' flag used with `%c' format", format_char);
-         /* To simplify the following code.  */
-         aflag = 0;
+         int y2k_level = 0;
+         if (strchr (fci->flags2, '4') != 0)
+           if (strchr (flag_chars, 'E') != 0)
+             y2k_level = 3;
+           else
+             y2k_level = 2;
+         else if (strchr (fci->flags2, '3') != 0)
+           y2k_level = 3;
+         else if (strchr (fci->flags2, '2') != 0)
+           y2k_level = 2;
+         if (y2k_level == 3)
+           status_warning (status, "`%%%c' yields only last 2 digits of year in some locales",
+                           format_char);
+         else if (y2k_level == 2)
+           status_warning (status, "`%%%c' yields only last 2 digits of year", format_char);
        }
-      /* The a flag is a GNU extension.  */
-      else if (pedantic && aflag)
-       warning ("ANSI C does not support the `a' flag");
-      if (info->format_type == scanf_format_type && format_char == '[')
+
+      if (strchr (fci->flags2, '[') != 0)
        {
          /* Skip over scan set, in case it happens to have '%' in it.  */
          if (*format_chars == '^')
@@ -1885,161 +3039,322 @@ check_format_info (info, params)
            ++format_chars;
          if (*format_chars != ']')
            /* The end of the format string was reached.  */
-           warning ("no closing `]' for `%%[' format");
+           status_warning (status, "no closing `]' for `%%[' format");
        }
-      if (suppressed)
+
+      wanted_type = 0;
+      wanted_type_name = 0;
+      if (fki->flags & FMT_FLAG_ARG_CONVERT)
        {
-         if (index (fci->flag_chars, '*') == 0)
-           warning ("suppression of `%c' conversion in format", format_char);
-         continue;
+         wanted_type = (fci->types[length_chars_val].type
+                        ? *fci->types[length_chars_val].type : 0);
+         wanted_type_name = fci->types[length_chars_val].name;
+         wanted_type_std = fci->types[length_chars_val].std;
+         if (wanted_type == 0)
+           {
+             status_warning (status, "use of `%s' length modifier with `%c' type character",
+                             length_chars, format_char);
+             /* Heuristic: skip one argument when an invalid length/type
+                combination is encountered.  */
+             arg_num++;
+             if (params == 0)
+               {
+                 status_warning (status, "too few arguments for format");
+                 return;
+               }
+             params = TREE_CHAIN (params);
+             continue;
+           }
+         else if (pedantic
+                  /* Warn if non-standard, provided it is more non-standard
+                     than the length and type characters that may already
+                     have been warned for.  */
+                  && wanted_type_std > length_chars_std
+                  && wanted_type_std > fci->std)
+           {
+             if (wanted_type_std > C_STD_VER)
+               status_warning (status, "%s does not support the `%%%s%c' %s format",
+                               C_STD_NAME (wanted_type_std), length_chars,
+                               format_char, fki->name);
+           }
        }
-      for (i = 0; flag_chars[i] != 0; ++i)
+
+      /* Finally. . .check type of argument against desired type!  */
+      if (info->first_arg_num == 0)
+       continue;
+      if ((fci->pointer_count == 0 && wanted_type == void_type_node)
+         || suppressed)
        {
-         if (index (fci->flag_chars, flag_chars[i]) == 0)
-           warning ("flag `%c' used with type `%c'",
-                    flag_chars[i], format_char);
+         if (main_arg_num != 0)
+           {
+             if (suppressed)
+               status_warning (status, "operand number specified with suppressed assignment");
+             else
+               status_warning (status, "operand number specified for format taking no argument");
+           }
        }
-      if (info->format_type == strftime_format_type)
-       continue;
-      if (precise && index (flag_chars, '0') != 0
-         && (format_char == 'd' || format_char == 'i'
-             || format_char == 'o' || format_char == 'u'
-             || format_char == 'x' || format_char == 'X'))
-       warning ("`0' flag ignored with precision specifier and `%c' format",
-                format_char);
-      switch (length_char)
+      else
        {
-       default: wanted_type = fci->nolen ? *(fci->nolen) : 0; break;
-       case 'H': wanted_type = fci->hhlen ? *(fci->hhlen) : 0; break;
-       case 'h': wanted_type = fci->hlen ? *(fci->hlen) : 0; break;
-       case 'l': wanted_type = fci->llen ? *(fci->llen) : 0; break;
-       case 'q': wanted_type = fci->qlen ? *(fci->qlen) : 0; break;
-       case 'L': wanted_type = fci->bigllen ? *(fci->bigllen) : 0; break;
-       case 'z': case 'Z': wanted_type = fci->zlen ? *fci->zlen : 0; break;
+         if (main_arg_num != 0)
+           {
+             arg_num = main_arg_num;
+             params = main_arg_params;
+           }
+         else
+           {
+             ++arg_num;
+             if (has_operand_number > 0)
+               {
+                 status_warning (status, "missing $ operand number in format");
+                 return;
+               }
+             else
+               has_operand_number = 0;
+             if (params == 0)
+               {
+                 status_warning (status, "too few arguments for format");
+                 return;
+               }
+           }
+         cur_param = TREE_VALUE (params);
+         params = TREE_CHAIN (params);
+         main_wanted_type.wanted_type = wanted_type;
+         main_wanted_type.wanted_type_name = wanted_type_name;
+         main_wanted_type.pointer_count = fci->pointer_count + aflag;
+         main_wanted_type.char_lenient_flag = 0;
+         if (strchr (fci->flags2, 'c') != 0)
+           main_wanted_type.char_lenient_flag = 1;
+         main_wanted_type.writing_in_flag = 0;
+         if (strchr (fci->flags2, 'W') != 0)
+           main_wanted_type.writing_in_flag = 1;
+         main_wanted_type.name = NULL;
+         main_wanted_type.param = cur_param;
+         main_wanted_type.arg_num = arg_num;
+         main_wanted_type.next = NULL;
+         if (last_wanted_type != 0)
+           last_wanted_type->next = &main_wanted_type;
+         if (first_wanted_type == 0)
+           first_wanted_type = &main_wanted_type;
+         last_wanted_type = &main_wanted_type;
        }
+
+      if (first_wanted_type != 0)
+       check_format_types (status, first_wanted_type);
+
+    }
+}
+
+
+/* Check the argument types from a single format conversion (possibly
+   including width and precision arguments).  */
+static void
+check_format_types (status, types)
+     int *status;
+     format_wanted_type *types;
+{
+  for (; types != 0; types = types->next)
+    {
+      tree cur_param;
+      tree cur_type;
+      tree orig_cur_type;
+      tree wanted_type;
+      tree promoted_type;
+      int arg_num;
+      int i;
+      int char_type_flag;
+      cur_param = types->param;
+      cur_type = TREE_TYPE (cur_param);
+      if (cur_type == error_mark_node)
+       continue;
+      char_type_flag = 0;
+      wanted_type = types->wanted_type;
+      arg_num = types->arg_num;
+
+      /* The following should not occur here.  */
       if (wanted_type == 0)
-       warning ("use of `%c' length character with `%c' type character",
-                length_char, format_char);
+       abort ();
+      if (wanted_type == void_type_node && types->pointer_count == 0)
+       abort ();
 
-      /* Finally. . .check type of argument against desired type!  */
-      if (info->first_arg_num == 0)
-       continue;
-      if (fci->pointer_count == 0 && wanted_type == void_type_node)
-       /* This specifier takes no argument.  */
-       continue;
-      if (params == 0)
+      if (types->pointer_count == 0)
        {
-         tfaff ();
-         return;
+         promoted_type = simple_type_promotes_to (wanted_type);
+         if (promoted_type != NULL_TREE)
+           wanted_type = promoted_type;
        }
-      cur_param = TREE_VALUE (params);
-      params = TREE_CHAIN (params);
-      ++arg_num;
-      cur_type = TREE_TYPE (cur_param);
 
       STRIP_NOPS (cur_param);
 
       /* Check the types of any additional pointer arguments
         that precede the "real" argument.  */
-      for (i = 0; i < fci->pointer_count + aflag; ++i)
+      for (i = 0; i < types->pointer_count; ++i)
        {
          if (TREE_CODE (cur_type) == POINTER_TYPE)
            {
              cur_type = TREE_TYPE (cur_type);
+             if (cur_type == error_mark_node)
+               break;
+
+             /* Check for writing through a NULL pointer.  */
+             if (types->writing_in_flag
+                 && i == 0
+                 && cur_param != 0
+                 && integer_zerop (cur_param))
+               status_warning (status,
+                               "writing through null pointer (arg %d)",
+                               arg_num);
 
              if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR)
                cur_param = TREE_OPERAND (cur_param, 0);
              else
                cur_param = 0;
 
-             continue;
+             /* See if this is an attempt to write into a const type with
+                scanf or with printf "%n".  Note: the writing in happens
+                at the first indirection only, if for example
+                void * const * is passed to scanf %p; passing
+                const void ** is simply passing an incompatible type.  */
+             if (types->writing_in_flag
+                 && i == 0
+                 && (TYPE_READONLY (cur_type)
+                     || (cur_param != 0
+                         && (TREE_CODE_CLASS (TREE_CODE (cur_param)) == 'c'
+                             || (DECL_P (cur_param)
+                                 && TREE_READONLY (cur_param))))))
+               status_warning (status, "writing into constant object (arg %d)", arg_num);
+
+             /* If there are extra type qualifiers beyond the first
+                indirection, then this makes the types technically
+                incompatible.  */
+             if (i > 0
+                 && pedantic
+                 && (TYPE_READONLY (cur_type)
+                     || TYPE_VOLATILE (cur_type)
+                     || TYPE_RESTRICT (cur_type)))
+               status_warning (status, "extra type qualifiers in format argument (arg %d)",
+                        arg_num);
+
            }
-         if (TREE_CODE (cur_type) != ERROR_MARK)
+         else
            {
-             if (fci->pointer_count + aflag == 1)
-               warning ("format argument is not a pointer (arg %d)", arg_num);
+             if (types->pointer_count == 1)
+               status_warning (status, "format argument is not a pointer (arg %d)", arg_num);
              else
-               warning ("format argument is not a pointer to a pointer (arg %d)", arg_num);
+               status_warning (status, "format argument is not a pointer to a pointer (arg %d)", arg_num);
+             break;
            }
-         break;
        }
 
-      /* See if this is an attempt to write into a const type with
-        scanf or with printf "%n".  */
-      if ((info->format_type == scanf_format_type
-          || (info->format_type == printf_format_type
-              && format_char == 'n'))
-         && i == fci->pointer_count + aflag
-         && wanted_type != 0
-         && TREE_CODE (cur_type) != ERROR_MARK
-         && (TYPE_READONLY (cur_type)
-             || (cur_param != 0
-                 && (TREE_CODE_CLASS (TREE_CODE (cur_param)) == 'c'
-                     || (DECL_P (cur_param) && TREE_READONLY (cur_param))))))
-       warning ("writing into constant object (arg %d)", arg_num);
+      if (i < types->pointer_count)
+       continue;
+
+      orig_cur_type = cur_type;
+      cur_type = TYPE_MAIN_VARIANT (cur_type);
+
+      /* Check whether the argument type is a character type.  This leniency
+        only applies to certain formats, flagged with 'c'.
+      */
+      if (types->char_lenient_flag)
+       char_type_flag = (cur_type == char_type_node
+                         || cur_type == signed_char_type_node
+                         || cur_type == unsigned_char_type_node);
 
       /* Check the type of the "real" argument, if there's a type we want.  */
-      if (i == fci->pointer_count + aflag && wanted_type != 0
-         && TREE_CODE (cur_type) != ERROR_MARK
-         && wanted_type != TYPE_MAIN_VARIANT (cur_type)
-         /* If we want `void *', allow any pointer type.
-            (Anything else would already have got a warning.)  */
-         && ! (wanted_type == void_type_node
-               && fci->pointer_count > 0)
-         /* Don't warn about differences merely in signedness.  */
-         && !(TREE_CODE (wanted_type) == INTEGER_TYPE
-              && TREE_CODE (TYPE_MAIN_VARIANT (cur_type)) == INTEGER_TYPE
-              && (TREE_UNSIGNED (wanted_type)
-                  ? wanted_type == (cur_type = unsigned_type (cur_type))
-                  : wanted_type == (cur_type = signed_type (cur_type))))
-         /* Likewise, "signed char", "unsigned char" and "char" are
-            equivalent but the above test won't consider them equivalent.  */
-         && ! (wanted_type == char_type_node
-               && (TYPE_MAIN_VARIANT (cur_type) == signed_char_type_node
-                   || TYPE_MAIN_VARIANT (cur_type) == unsigned_char_type_node)))
-       {
-         register const char *this;
-         register const char *that;
-
-         this = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (wanted_type)));
-         that = 0;
-         if (TREE_CODE (cur_type) != ERROR_MARK
-             && TYPE_NAME (cur_type) != 0
-             && TREE_CODE (cur_type) != INTEGER_TYPE
-             && !(TREE_CODE (cur_type) == POINTER_TYPE
-                  && TREE_CODE (TREE_TYPE (cur_type)) == INTEGER_TYPE))
-           {
-             if (TREE_CODE (TYPE_NAME (cur_type)) == TYPE_DECL
-                 && DECL_NAME (TYPE_NAME (cur_type)) != 0)
-               that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (cur_type)));
-             else
-               that = IDENTIFIER_POINTER (TYPE_NAME (cur_type));
-           }
+      if (wanted_type == cur_type)
+       continue;
+      /* If we want `void *', allow any pointer type.
+        (Anything else would already have got a warning.)
+        With -pedantic, only allow pointers to void and to character
+        types.  */
+      if (wanted_type == void_type_node
+         && (!pedantic || (i == 1 && char_type_flag)))
+       continue;
+      /* Don't warn about differences merely in signedness, unless
+        -pedantic.  With -pedantic, warn if the type is a pointer
+        target and not a character type, and for character types at
+        a second level of indirection.  */
+      if (TREE_CODE (wanted_type) == INTEGER_TYPE
+         && TREE_CODE (cur_type) == INTEGER_TYPE
+         && (! pedantic || i == 0 || (i == 1 && char_type_flag))
+         && (TREE_UNSIGNED (wanted_type)
+             ? wanted_type == unsigned_type (cur_type)
+             : wanted_type == signed_type (cur_type)))
+       continue;
+      /* Likewise, "signed char", "unsigned char" and "char" are
+        equivalent but the above test won't consider them equivalent.  */
+      if (wanted_type == char_type_node
+         && (! pedantic || i < 2)
+         && char_type_flag)
+       continue;
+      /* Now we have a type mismatch.  */
+      {
+       register const char *this;
+       register const char *that;
+
+       this = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (wanted_type)));
+       that = 0;
+       if (TYPE_NAME (orig_cur_type) != 0
+           && TREE_CODE (orig_cur_type) != INTEGER_TYPE
+           && !(TREE_CODE (orig_cur_type) == POINTER_TYPE
+                && TREE_CODE (TREE_TYPE (orig_cur_type)) == INTEGER_TYPE))
+         {
+           if (TREE_CODE (TYPE_NAME (orig_cur_type)) == TYPE_DECL
+               && DECL_NAME (TYPE_NAME (orig_cur_type)) != 0)
+             that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (orig_cur_type)));
+           else
+             that = IDENTIFIER_POINTER (TYPE_NAME (orig_cur_type));
+         }
 
-         /* A nameless type can't possibly match what the format wants.
-            So there will be a warning for it.
-            Make up a string to describe vaguely what it is.  */
-         if (that == 0)
-           {
-             if (TREE_CODE (cur_type) == POINTER_TYPE)
-               that = "pointer";
-             else
-               that = "different type";
-           }
+       /* A nameless type can't possibly match what the format wants.
+          So there will be a warning for it.
+          Make up a string to describe vaguely what it is.  */
+       if (that == 0)
+         {
+           if (TREE_CODE (orig_cur_type) == POINTER_TYPE)
+             that = "pointer";
+           else
+             that = "different type";
+         }
 
-         /* Make the warning better in case of mismatch of int vs long.  */
-         if (TREE_CODE (cur_type) == INTEGER_TYPE
-             && TREE_CODE (wanted_type) == INTEGER_TYPE
-             && TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type)
-             && TYPE_NAME (cur_type) != 0
-             && TREE_CODE (TYPE_NAME (cur_type)) == TYPE_DECL)
-           that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (cur_type)));
+       /* Make the warning better in case of mismatch of int vs long.  */
+       if (TREE_CODE (orig_cur_type) == INTEGER_TYPE
+           && TREE_CODE (wanted_type) == INTEGER_TYPE
+           && TYPE_PRECISION (orig_cur_type) == TYPE_PRECISION (wanted_type)
+           && TYPE_NAME (orig_cur_type) != 0
+           && TREE_CODE (TYPE_NAME (orig_cur_type)) == TYPE_DECL)
+         that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (orig_cur_type)));
 
-         if (strcmp (this, that) != 0)
-           warning ("%s format, %s arg (arg %d)", this, that, arg_num);
-       }
+       if (strcmp (this, that) != 0)
+         {
+           /* There may be a better name for the format, e.g. size_t,
+              but we should allow for programs with a perverse typedef
+              making size_t something other than what the compiler
+              thinks.  */
+           if (types->wanted_type_name != 0
+               && strcmp (types->wanted_type_name, that) != 0)
+             this = types->wanted_type_name;
+           if (types->name != 0)
+             status_warning (status, "%s is not type %s (arg %d)", types->name, this,
+                      arg_num);
+           else
+             status_warning (status, "%s format, %s arg (arg %d)", this, that, arg_num);
+         }
+      }
     }
 }
+
+/* Set format warning options according to a -Wformat=n option.  */
+
+void
+set_Wformat (setting)
+     int setting;
+{
+  warn_format = setting;
+  warn_format_y2k = setting;
+  warn_format_extra_args = setting;
+  if (setting != 1)
+    warn_format_nonliteral = setting;
+}
 \f
 /* Print a warning if a constant expression had overflow in folding.
    Invoke this function on every expression that the language
@@ -2148,6 +3463,415 @@ convert_and_check (type, expr)
   return t;
 }
 \f
+/* A node in a list that describes references to variables (EXPR), which are
+   either read accesses if WRITER is zero, or write accesses, in which case
+   WRITER is the parent of EXPR.  */
+struct tlist
+{
+  struct tlist *next;
+  tree expr, writer;
+};
+
+/* Used to implement a cache the results of a call to verify_tree.  We only
+   use this for SAVE_EXPRs.  */
+struct tlist_cache
+{
+  struct tlist_cache *next;
+  struct tlist *cache_before_sp;
+  struct tlist *cache_after_sp;
+  tree expr;
+};
+
+/* Obstack to use when allocating tlist structures, and corresponding
+   firstobj.  */
+static struct obstack tlist_obstack;
+static char *tlist_firstobj = 0;
+
+/* Keep track of the identifiers we've warned about, so we can avoid duplicate
+   warnings.  */
+static struct tlist *warned_ids;
+/* SAVE_EXPRs need special treatment.  We process them only once and then
+   cache the results.  */
+static struct tlist_cache *save_expr_cache;
+
+static void add_tlist PARAMS ((struct tlist **, struct tlist *, tree, int));
+static void merge_tlist PARAMS ((struct tlist **, struct tlist *, int));
+static void verify_tree PARAMS ((tree, struct tlist **, struct tlist **, tree));
+static int warning_candidate_p PARAMS ((tree));
+static void warn_for_collisions PARAMS ((struct tlist *));
+static void warn_for_collisions_1 PARAMS ((tree, tree, struct tlist *, int));
+static struct tlist *new_tlist PARAMS ((struct tlist *, tree, tree));
+static void verify_sequence_points PARAMS ((tree));
+
+/* Create a new struct tlist and fill in its fields.  */
+static struct tlist *
+new_tlist (next, t, writer)
+     struct tlist *next;
+     tree t;
+     tree writer;
+{
+  struct tlist *l;
+  l = (struct tlist *) obstack_alloc (&tlist_obstack, sizeof *l);
+  l->next = next;
+  l->expr = t;
+  l->writer = writer;
+  return l;
+}
+
+/* Add duplicates of the nodes found in ADD to the list *TO.  If EXCLUDE_WRITER
+   is nonnull, we ignore any node we find which has a writer equal to it.  */
+
+static void
+add_tlist (to, add, exclude_writer, copy)
+     struct tlist **to;
+     struct tlist *add;
+     tree exclude_writer;
+     int copy;
+{
+  while (add)
+    {
+      struct tlist *next = add->next;
+      if (! copy)
+       add->next = *to;
+      if (! exclude_writer || add->writer != exclude_writer)
+       *to = copy ? new_tlist (*to, add->expr, add->writer) : add;
+      add = next;
+    }
+}
+
+/* Merge the nodes of ADD into TO.  This merging process is done so that for
+   each variable that already exists in TO, no new node is added; however if
+   there is a write access recorded in ADD, and an occurrence on TO is only
+   a read access, then the occurrence in TO will be modified to record the
+   write.  */
+
+static void
+merge_tlist (to, add, copy)
+     struct tlist **to;
+     struct tlist *add;
+     int copy;
+{
+  struct tlist **end = to;
+
+  while (*end)
+    end = &(*end)->next;
+
+  while (add)
+    {
+      int found = 0;
+      struct tlist *tmp2;
+      struct tlist *next = add->next;
+
+      for (tmp2 = *to; tmp2; tmp2 = tmp2->next)
+       if (tmp2->expr == add->expr)
+         {
+           found = 1;
+           if (! tmp2->writer)
+             tmp2->writer = add->writer;
+         }
+      if (! found)
+       {
+         *end = copy ? add : new_tlist (NULL, add->expr, add->writer);
+         end = &(*end)->next;
+         *end = 0;
+       }
+      add = next;
+    }
+}
+
+/* WRITTEN is a variable, WRITER is its parent.  Warn if any of the variable
+   references in list LIST conflict with it, excluding reads if ONLY writers
+   is nonzero.  */
+
+static void
+warn_for_collisions_1 (written, writer, list, only_writes)
+     tree written, writer;
+     struct tlist *list;
+     int only_writes;
+{
+  struct tlist *tmp;
+
+  /* Avoid duplicate warnings.  */
+  for (tmp = warned_ids; tmp; tmp = tmp->next)
+    if (tmp->expr == written)
+      return;
+
+  while (list)
+    {
+      if (list->expr == written
+         && list->writer != writer
+         && (! only_writes || list->writer))
+       {
+         warned_ids = new_tlist (warned_ids, written, NULL_TREE);
+         warning ("operation on `%s' may be undefined",
+                  IDENTIFIER_POINTER (DECL_NAME (list->expr)));
+       }
+      list = list->next;
+    }
+}
+
+/* Given a list LIST of references to variables, find whether any of these
+   can cause conflicts due to missing sequence points.  */
+
+static void
+warn_for_collisions (list)
+     struct tlist *list;
+{
+  struct tlist *tmp;
+  
+  for (tmp = list; tmp; tmp = tmp->next)
+    {
+      if (tmp->writer)
+       warn_for_collisions_1 (tmp->expr, tmp->writer, list, 0);
+    }
+}
+
+/* Return nonzero if X is a tree that can be verified by the sequence poitn
+   warnings.  */
+static int
+warning_candidate_p (x)
+     tree x;
+{
+  return TREE_CODE (x) == VAR_DECL || TREE_CODE (x) == PARM_DECL;
+}
+
+/* Walk the tree X, and record accesses to variables.  If X is written by the
+   parent tree, WRITER is the parent.
+   We store accesses in one of the two lists: PBEFORE_SP, and PNO_SP.  If this
+   expression or its only operand forces a sequence point, then everything up
+   to the sequence point is stored in PBEFORE_SP.  Everything else gets stored
+   in PNO_SP.
+   Once we return, we will have emitted warnings if any subexpression before
+   such a sequence point could be undefined.  On a higher level, however, the
+   sequence point may not be relevant, and we'll merge the two lists.
+
+   Example: (b++, a) + b;
+   The call that processes the COMPOUND_EXPR will store the increment of B
+   in PBEFORE_SP, and the use of A in PNO_SP.  The higher-level call that
+   processes the PLUS_EXPR will need to merge the two lists so that
+   eventually, all accesses end up on the same list (and we'll warn about the
+   unordered subexpressions b++ and b.
+
+   A note on merging.  If we modify the former example so that our expression
+   becomes
+     (b++, b) + a
+   care must be taken not simply to add all three expressions into the final
+   PNO_SP list.  The function merge_tlist takes care of that by merging the
+   before-SP list of the COMPOUND_EXPR into its after-SP list in a special
+   way, so that no more than one access to B is recorded.  */
+
+static void
+verify_tree (x, pbefore_sp, pno_sp, writer)
+     tree x;
+     struct tlist **pbefore_sp, **pno_sp;
+     tree writer;
+{
+  struct tlist *tmp_before, *tmp_nosp, *tmp_list2, *tmp_list3;
+  enum tree_code code;
+  char class;
+
+ restart:
+  code = TREE_CODE (x);
+  class = TREE_CODE_CLASS (code);
+
+  if (warning_candidate_p (x))
+    {
+      *pno_sp = new_tlist (*pno_sp, x, writer);
+      return;
+    }
+
+  switch (code)
+    {
+    case CONSTRUCTOR:
+      return;
+
+    case COMPOUND_EXPR:
+    case TRUTH_ANDIF_EXPR:
+    case TRUTH_ORIF_EXPR:
+      tmp_before = tmp_nosp = tmp_list3 = 0;
+      verify_tree (TREE_OPERAND (x, 0), &tmp_before, &tmp_nosp, NULL_TREE);
+      warn_for_collisions (tmp_nosp);
+      merge_tlist (pbefore_sp, tmp_before, 0);
+      merge_tlist (pbefore_sp, tmp_nosp, 0);
+      verify_tree (TREE_OPERAND (x, 1), &tmp_list3, pno_sp, NULL_TREE);
+      merge_tlist (pbefore_sp, tmp_list3, 0);
+      return;
+
+    case COND_EXPR:
+      tmp_before = tmp_list2 = 0;
+      verify_tree (TREE_OPERAND (x, 0), &tmp_before, &tmp_list2, NULL_TREE);
+      warn_for_collisions (tmp_list2);
+      merge_tlist (pbefore_sp, tmp_before, 0);
+      merge_tlist (pbefore_sp, tmp_list2, 1);
+
+      tmp_list3 = tmp_nosp = 0;
+      verify_tree (TREE_OPERAND (x, 1), &tmp_list3, &tmp_nosp, NULL_TREE);
+      warn_for_collisions (tmp_nosp);
+      merge_tlist (pbefore_sp, tmp_list3, 0);
+
+      tmp_list3 = tmp_list2 = 0;
+      verify_tree (TREE_OPERAND (x, 2), &tmp_list3, &tmp_list2, NULL_TREE);
+      warn_for_collisions (tmp_list2);
+      merge_tlist (pbefore_sp, tmp_list3, 0);
+      /* Rather than add both tmp_nosp and tmp_list2, we have to merge the
+        two first, to avoid warning for (a ? b++ : b++).  */
+      merge_tlist (&tmp_nosp, tmp_list2, 0);
+      add_tlist (pno_sp, tmp_nosp, NULL_TREE, 0);
+      return;
+
+    case PREDECREMENT_EXPR:
+    case PREINCREMENT_EXPR:
+    case POSTDECREMENT_EXPR:
+    case POSTINCREMENT_EXPR:
+      verify_tree (TREE_OPERAND (x, 0), pno_sp, pno_sp, x);
+      return;
+
+    case MODIFY_EXPR:
+      tmp_before = tmp_nosp = tmp_list3 = 0;
+      verify_tree (TREE_OPERAND (x, 1), &tmp_before, &tmp_nosp, NULL_TREE);
+      verify_tree (TREE_OPERAND (x, 0), &tmp_list3, &tmp_list3, x);
+      /* Expressions inside the LHS are not ordered wrt. the sequence points
+        in the RHS.  Example:
+          *a = (a++, 2)
+        Despite the fact that the modification of "a" is in the before_sp
+        list (tmp_before), it conflicts with the use of "a" in the LHS.
+        We can handle this by adding the contents of tmp_list3
+        to those of tmp_before, and redoing the collision warnings for that
+        list.  */
+      add_tlist (&tmp_before, tmp_list3, x, 1);
+      warn_for_collisions (tmp_before);
+      /* Exclude the LHS itself here; we first have to merge it into the
+        tmp_nosp list.  This is done to avoid warning for "a = a"; if we
+        didn't exclude the LHS, we'd get it twice, once as a read and once
+        as a write.  */
+      add_tlist (pno_sp, tmp_list3, x, 0);
+      warn_for_collisions_1 (TREE_OPERAND (x, 0), x, tmp_nosp, 1);
+
+      merge_tlist (pbefore_sp, tmp_before, 0);
+      if (warning_candidate_p (TREE_OPERAND (x, 0)))
+       merge_tlist (&tmp_nosp, new_tlist (NULL, TREE_OPERAND (x, 0), x), 0);
+      add_tlist (pno_sp, tmp_nosp, NULL_TREE, 1);
+      return;
+
+    case CALL_EXPR:
+      /* We need to warn about conflicts among arguments and conflicts between
+        args and the function address.  Side effects of the function address,
+        however, are not ordered by the sequence point of the call.  */
+      tmp_before = tmp_nosp = tmp_list2 = tmp_list3 = 0;
+      verify_tree (TREE_OPERAND (x, 0), &tmp_before, &tmp_nosp, NULL_TREE);
+      if (TREE_OPERAND (x, 1))
+       verify_tree (TREE_OPERAND (x, 1), &tmp_list2, &tmp_list3, NULL_TREE);
+      merge_tlist (&tmp_list3, tmp_list2, 0);
+      add_tlist (&tmp_before, tmp_list3, NULL_TREE, 0);
+      add_tlist (&tmp_before, tmp_nosp, NULL_TREE, 0);
+      warn_for_collisions (tmp_before);
+      add_tlist (pbefore_sp, tmp_before, NULL_TREE, 0);
+      return;
+
+    case TREE_LIST:
+      /* Scan all the list, e.g. indices of multi dimensional array.  */
+      while (x)
+       {
+         tmp_before = tmp_nosp = 0;
+         verify_tree (TREE_VALUE (x), &tmp_before, &tmp_nosp, NULL_TREE);
+         merge_tlist (&tmp_nosp, tmp_before, 0);
+         add_tlist (pno_sp, tmp_nosp, NULL_TREE, 0);
+         x = TREE_CHAIN (x);
+       }
+      return;
+
+    case SAVE_EXPR:
+      {
+       struct tlist_cache *t;
+       for (t = save_expr_cache; t; t = t->next)
+         if (t->expr == x)
+           break;
+
+       if (! t)
+         {
+           t = (struct tlist_cache *) obstack_alloc (&tlist_obstack,
+                                                     sizeof *t);
+           t->next = save_expr_cache;
+           t->expr = x;
+           save_expr_cache = t;
+
+           tmp_before = tmp_nosp = 0;
+           verify_tree (TREE_OPERAND (x, 0), &tmp_before, &tmp_nosp, NULL_TREE);
+           warn_for_collisions (tmp_nosp);
+
+           tmp_list3 = 0;
+           while (tmp_nosp)
+             {
+               struct tlist *t = tmp_nosp;
+               tmp_nosp = t->next;
+               merge_tlist (&tmp_list3, t, 0);
+             }
+           t->cache_before_sp = tmp_before;
+           t->cache_after_sp = tmp_list3;
+         }
+       merge_tlist (pbefore_sp, t->cache_before_sp, 1);
+       add_tlist (pno_sp, t->cache_after_sp, NULL_TREE, 1);
+       return;
+      }
+    default:
+      break;
+    }
+
+  if (class == '1')
+    {
+      if (first_rtl_op (code) == 0)
+       return;
+      x = TREE_OPERAND (x, 0);
+      writer = 0;
+      goto restart;
+    }
+
+  switch (class)
+    {
+    case 'r':
+    case '<':
+    case '2':
+    case 'b':
+    case 'e':
+    case 's':
+    case 'x':
+      {
+       int lp;
+       int max = first_rtl_op (TREE_CODE (x));
+       for (lp = 0; lp < max; lp++)
+         {
+           tmp_before = tmp_nosp = 0;
+           verify_tree (TREE_OPERAND (x, lp), &tmp_before, &tmp_nosp, NULL_TREE);
+           merge_tlist (&tmp_nosp, tmp_before, 0);
+           add_tlist (pno_sp, tmp_nosp, NULL_TREE, 0);
+         }
+       break;
+      }
+    }
+}
+
+/* Try to warn for undefined behaviour in EXPR due to missing sequence
+   points.  */
+
+static void
+verify_sequence_points (expr)
+     tree expr;
+{
+  struct tlist *before_sp = 0, *after_sp = 0;
+
+  warned_ids = 0;
+  save_expr_cache = 0;
+  if (tlist_firstobj == 0)
+    {
+      gcc_obstack_init (&tlist_obstack);
+      tlist_firstobj = obstack_alloc (&tlist_obstack, 0);
+    }
+
+  verify_tree (expr, &before_sp, &after_sp, 0);
+  warn_for_collisions (after_sp);
+  obstack_free (&tlist_obstack, tlist_firstobj);
+}
+
 void
 c_expand_expr_stmt (expr)
      tree expr;
@@ -2158,12 +3882,16 @@ c_expand_expr_stmt (expr)
       || TREE_CODE (TREE_TYPE (expr)) == FUNCTION_TYPE)
     expr = default_conversion (expr);
 
+  if (warn_sequence_point)
+    verify_sequence_points (expr);
+
   if (TREE_TYPE (expr) != error_mark_node
       && !COMPLETE_OR_VOID_TYPE_P (TREE_TYPE (expr))
       && TREE_CODE (TREE_TYPE (expr)) != ARRAY_TYPE)
     error ("expression statement has incomplete type");
 
-  expand_expr_stmt (expr);
+  last_expr_type = TREE_TYPE (expr); 
+  add_stmt (build_stmt (EXPR_STMT, expr));
 }
 \f
 /* Validate the expression after `case' and apply default promotions.  */
@@ -2177,6 +3905,18 @@ check_case_value (value)
 
   /* Strip NON_LVALUE_EXPRs since we aren't using as an lvalue.  */
   STRIP_TYPE_NOPS (value);
+  /* In C++, the following is allowed:
+
+       const int i = 3;
+       switch (...) { case i: ... }
+
+     So, we try to reduce the VALUE to a constant that way.  */
+  if (c_language == clk_cplusplus)
+    {
+      value = decl_constant_value (value);
+      STRIP_TYPE_NOPS (value);
+      value = fold (value);
+    }
 
   if (TREE_CODE (value) != INTEGER_CST
       && value != error_mark_node)
@@ -2296,6 +4036,19 @@ type_for_mode (mode, unsignedp)
   if (mode == TYPE_MODE (build_pointer_type (integer_type_node)))
     return build_pointer_type (integer_type_node);
 
+#ifdef VECTOR_MODE_SUPPORTED_P
+  if (mode == TYPE_MODE (V4SF_type_node) && VECTOR_MODE_SUPPORTED_P (mode))
+    return V4SF_type_node;
+  if (mode == TYPE_MODE (V4SI_type_node) && VECTOR_MODE_SUPPORTED_P (mode))
+    return V4SI_type_node;
+  if (mode == TYPE_MODE (V2SI_type_node) && VECTOR_MODE_SUPPORTED_P (mode))
+    return V2SI_type_node;
+  if (mode == TYPE_MODE (V4HI_type_node) && VECTOR_MODE_SUPPORTED_P (mode))
+    return V4HI_type_node;
+  if (mode == TYPE_MODE (V8QI_type_node) && VECTOR_MODE_SUPPORTED_P (mode))
+    return V8QI_type_node;
+#endif
+
   return 0;
 }
 
@@ -2797,7 +4550,7 @@ shorten_compare (op0_ptr, op1_ptr, restype_ptr, rescode_ptr)
                 are requested.  However, if OP0 is a constant that is
                 >= 0, the signedness of the comparison isn't an issue,
                 so suppress the warning.  */
-             if (extra_warnings
+             if (extra_warnings && !in_system_header
                  && ! (TREE_CODE (primop0) == INTEGER_CST
                        && ! TREE_OVERFLOW (convert (signed_type (type),
                                                     primop0))))
@@ -2806,7 +4559,7 @@ shorten_compare (op0_ptr, op1_ptr, restype_ptr, rescode_ptr)
              break;
 
            case LT_EXPR:
-             if (extra_warnings
+             if (extra_warnings && !in_system_header
                  && ! (TREE_CODE (primop0) == INTEGER_CST
                        && ! TREE_OVERFLOW (convert (signed_type (type),
                                                     primop0))))
@@ -2988,205 +4741,22 @@ truthvalue_conversion (expr)
       break;
 
     default:
-      break;
-    }
-
-  if (TREE_CODE (TREE_TYPE (expr)) == COMPLEX_TYPE)
-    {
-      tree tem = save_expr (expr);
-      return (build_binary_op
-             ((TREE_SIDE_EFFECTS (expr)
-               ? TRUTH_OR_EXPR : TRUTH_ORIF_EXPR),
-              truthvalue_conversion (build_unary_op (REALPART_EXPR, tem, 0)),
-              truthvalue_conversion (build_unary_op (IMAGPART_EXPR, tem, 0)),
-              0));
-    }
-
-  return build_binary_op (NE_EXPR, expr, integer_zero_node, 1);
-}
-\f
-#if USE_CPPLIB
-/* Read the rest of a #-directive from input stream FINPUT.
-   In normal use, the directive name and the white space after it
-   have already been read, so they won't be included in the result.
-   We allow for the fact that the directive line may contain
-   a newline embedded within a character or string literal which forms
-   a part of the directive.
-
-   The value is a string in a reusable buffer.  It remains valid
-   only until the next time this function is called.  */
-unsigned char *yy_cur, *yy_lim;
-
-#define GETC() (yy_cur < yy_lim ? *yy_cur++ : yy_get_token ())
-#define UNGETC(c) ((c) == EOF ? 0 : yy_cur--)
-
-int
-yy_get_token ()
-{
-  for (;;)
-    {
-      parse_in.limit = parse_in.token_buffer;
-      cpp_token = cpp_get_token (&parse_in);
-      if (cpp_token == CPP_EOF)
-       return -1;
-      yy_lim = CPP_PWRITTEN (&parse_in);
-      yy_cur = parse_in.token_buffer;
-      if (yy_cur < yy_lim)
-       return *yy_cur++;
-    }
-}
-
-char *
-get_directive_line ()
-{
-  static char *directive_buffer = NULL;
-  static unsigned buffer_length = 0;
-  register char *p;
-  register char *buffer_limit;
-  register int looking_for = 0;
-  register int char_escaped = 0;
-
-  if (buffer_length == 0)
-    {
-      directive_buffer = (char *)xmalloc (128);
-      buffer_length = 128;
-    }
-
-  buffer_limit = &directive_buffer[buffer_length];
-
-  for (p = directive_buffer; ; )
-    {
-      int c;
-
-      /* Make buffer bigger if it is full.  */
-      if (p >= buffer_limit)
-        {
-         register unsigned bytes_used = (p - directive_buffer);
-
-         buffer_length *= 2;
-         directive_buffer
-           = (char *)xrealloc (directive_buffer, buffer_length);
-         p = &directive_buffer[bytes_used];
-         buffer_limit = &directive_buffer[buffer_length];
-        }
-
-      c = GETC ();
-
-      /* Discard initial whitespace.  */
-      if ((c == ' ' || c == '\t') && p == directive_buffer)
-       continue;
-
-      /* Detect the end of the directive.  */
-      if (c == '\n' && looking_for == 0)
-       {
-          UNGETC (c);
-         c = '\0';
-       }
-
-      *p++ = c;
-
-      if (c == 0)
-       return directive_buffer;
-
-      /* Handle string and character constant syntax.  */
-      if (looking_for)
-       {
-         if (looking_for == c && !char_escaped)
-           looking_for = 0;    /* Found terminator... stop looking.  */
-       }
-      else
-        if (c == '\'' || c == '"')
-         looking_for = c;      /* Don't stop buffering until we see another
-                                  another one of these (or an EOF).  */
-
-      /* Handle backslash.  */
-      char_escaped = (c == '\\' && ! char_escaped);
-    }
-}
-#else
-/* Read the rest of a #-directive from input stream FINPUT.
-   In normal use, the directive name and the white space after it
-   have already been read, so they won't be included in the result.
-   We allow for the fact that the directive line may contain
-   a newline embedded within a character or string literal which forms
-   a part of the directive.
-
-   The value is a string in a reusable buffer.  It remains valid
-   only until the next time this function is called.
-
-   The terminating character ('\n' or EOF) is left in FINPUT for the
-   caller to re-read.  */
-
-char *
-get_directive_line (finput)
-     register FILE *finput;
-{
-  static char *directive_buffer = NULL;
-  static unsigned buffer_length = 0;
-  register char *p;
-  register char *buffer_limit;
-  register int looking_for = 0;
-  register int char_escaped = 0;
-
-  if (buffer_length == 0)
-    {
-      directive_buffer = (char *)xmalloc (128);
-      buffer_length = 128;
-    }
-
-  buffer_limit = &directive_buffer[buffer_length];
-
-  for (p = directive_buffer; ; )
-    {
-      int c;
-
-      /* Make buffer bigger if it is full.  */
-      if (p >= buffer_limit)
-        {
-         register unsigned bytes_used = (p - directive_buffer);
-
-         buffer_length *= 2;
-         directive_buffer
-           = (char *)xrealloc (directive_buffer, buffer_length);
-         p = &directive_buffer[bytes_used];
-         buffer_limit = &directive_buffer[buffer_length];
-        }
-
-      c = getc (finput);
-
-      /* Discard initial whitespace.  */
-      if ((c == ' ' || c == '\t') && p == directive_buffer)
-       continue;
-
-      /* Detect the end of the directive.  */
-      if (looking_for == 0
-         && (c == '\n' || c == EOF))
-       {
-          ungetc (c, finput);
-         c = '\0';
-       }
-
-      *p++ = c;
-
-      if (c == 0)
-       return directive_buffer;
-
-      /* Handle string and character constant syntax.  */
-      if (looking_for)
-       {
-         if (looking_for == c && !char_escaped)
-           looking_for = 0;    /* Found terminator... stop looking.  */
-       }
-      else
-        if (c == '\'' || c == '"')
-         looking_for = c;      /* Don't stop buffering until we see another
-                                  one of these (or an EOF).  */
+      break;
+    }
 
-      /* Handle backslash.  */
-      char_escaped = (c == '\\' && ! char_escaped);
+  if (TREE_CODE (TREE_TYPE (expr)) == COMPLEX_TYPE)
+    {
+      tree tem = save_expr (expr);
+      return (build_binary_op
+             ((TREE_SIDE_EFFECTS (expr)
+               ? TRUTH_OR_EXPR : TRUTH_ORIF_EXPR),
+              truthvalue_conversion (build_unary_op (REALPART_EXPR, tem, 0)),
+              truthvalue_conversion (build_unary_op (IMAGPART_EXPR, tem, 0)),
+              0));
     }
+
+  return build_binary_op (NE_EXPR, expr, integer_zero_node, 1);
 }
-#endif /* !USE_CPPLIB */
 \f
 /* Make a variant type in the proper way for C/C++, propagating qualifiers
    down to the element type of an array.  */
@@ -3338,9 +4908,7 @@ lang_get_alias_set (t)
         can dereference IPP and CIPP.  So, we ignore cv-qualifiers on
         the pointed-to types.  This issue has been reported to the
         C++ committee.  */
-      t1 = TYPE_MAIN_VARIANT (TREE_TYPE (t));
-      t1 = ((TREE_CODE (t) == POINTER_TYPE)
-          ? build_pointer_type (t1) : build_reference_type (t1));
+      t1 = build_type_no_quals (t);
       if (t1 != t)
        return get_alias_set (t1);
     }
@@ -3353,29 +4921,27 @@ lang_get_alias_set (t)
 }
 
 /* Build tree nodes and builtin functions common to both C and C++ language
-   frontends.
-   CPLUS_MODE is nonzero if we are called from the C++ frontend, we generate
-   some stricter prototypes in that case.
-   NO_BUILTINS and NO_NONANSI_BUILTINS contain the respective values of
-   the language frontend flags flag_no_builtin and
-   flag_no_nonansi_builtin.  */
+   frontends.  */
 
 void
-c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
-    int cplus_mode, no_builtins, no_nonansi_builtins;
+c_common_nodes_and_builtins ()
 {
   tree temp;
   tree memcpy_ftype, memset_ftype, strlen_ftype;
-  tree bzero_ftype, bcmp_ftype;
+  tree bzero_ftype, bcmp_ftype, puts_ftype, printf_ftype;
   tree endlink, int_endlink, double_endlink, unsigned_endlink;
   tree sizetype_endlink;
   tree ptr_ftype, ptr_ftype_unsigned;
-  tree void_ftype_any, void_ftype_int, int_ftype_any;
+  tree void_ftype_any, void_ftype_int, int_ftype_any, sizet_ftype_any;
   tree double_ftype_double, double_ftype_double_double;
   tree float_ftype_float, ldouble_ftype_ldouble;
   tree int_ftype_cptr_cptr_sizet;
   tree int_ftype_string_string, string_ftype_ptr_ptr;
+  tree string_ftype_string_int, string_ftype_string_string;
+  tree string_ftype_string_cstring_sizet, int_ftype_cstring_cstring_sizet;
   tree long_ftype_long;
+  tree longlong_ftype_longlong;
+  tree intmax_ftype_intmax;
   /* Either char* or void*.  */
   tree traditional_ptr_type_node;
   /* Either const char* or const void*.  */
@@ -3385,6 +4951,23 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
   tree va_list_ref_type_node;
   tree va_list_arg_type_node;
 
+  string_type_node = build_pointer_type (char_type_node);
+  const_string_type_node
+    = build_pointer_type (build_type_variant (char_type_node, 1, 0));
+
+  wint_type_node =
+    TREE_TYPE (identifier_global_value (get_identifier (WINT_TYPE)));
+
+  intmax_type_node =
+    TREE_TYPE (identifier_global_value (get_identifier (INTMAX_TYPE)));
+  uintmax_type_node =
+    TREE_TYPE (identifier_global_value (get_identifier (UINTMAX_TYPE)));
+
+  default_function_type = build_function_type (integer_type_node, NULL_TREE);
+  ptrdiff_type_node
+    = TREE_TYPE (identifier_global_value (get_identifier (PTRDIFF_TYPE)));
+  unsigned_ptrdiff_type_node = unsigned_type (ptrdiff_type_node);
+
   pushdecl (build_decl (TYPE_DECL, get_identifier ("__builtin_va_list"),
                        va_list_type_node));
 
@@ -3416,6 +4999,7 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
   /* We realloc here because sizetype could be int or unsigned.  S'ok.  */
   ptr_ftype_sizetype = build_function_type (ptr_type_node, sizetype_endlink);
 
+  sizet_ftype_any = build_function_type (sizetype, NULL_TREE);
   int_ftype_any = build_function_type (integer_type_node, NULL_TREE);
   void_ftype_any = build_function_type (void_type_node, NULL_TREE);
   void_ftype = build_function_type (void_type_node, endlink);
@@ -3449,6 +5033,16 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                           tree_cons (NULL_TREE, long_integer_type_node,
                                      endlink));
 
+  longlong_ftype_longlong
+    = build_function_type (long_long_integer_type_node,
+                          tree_cons (NULL_TREE, long_long_integer_type_node,
+                                     endlink));
+
+  intmax_ftype_intmax
+    = build_function_type (intmax_type_node,
+                          tree_cons (NULL_TREE, intmax_type_node,
+                                     endlink));
+
   int_ftype_cptr_cptr_sizet
     = build_function_type (integer_type_node,
                           tree_cons (NULL_TREE, const_ptr_type_node,
@@ -3457,6 +5051,9 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                                                            sizetype,
                                                            endlink))));
 
+  void_zero_node = build_int_2 (0, 0);
+  TREE_TYPE (void_zero_node) = void_type_node;
+
   /* Prototype for strcpy.  */
   string_ftype_ptr_ptr
     = build_function_type (string_type_node,
@@ -3465,7 +5062,16 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                                                 const_string_type_node,
                                                 endlink)));
 
-  traditional_len_type_node = (flag_traditional && ! cplus_mode
+  /* Prototype for strncpy.  */
+  string_ftype_string_cstring_sizet
+    = build_function_type (string_type_node,
+                          tree_cons (NULL_TREE, string_type_node,
+                                     tree_cons (NULL_TREE,
+                                                const_string_type_node,
+                                                sizetype_endlink)));
+
+  traditional_len_type_node = ((flag_traditional && 
+                               c_language != clk_cplusplus)
                               ? integer_type_node : sizetype);
   traditional_len_endlink = tree_cons (NULL_TREE, traditional_len_type_node,
                                       endlink);
@@ -3478,15 +5084,41 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                                                 const_string_type_node,
                                                 endlink)));
 
+  /* Prototype for strncmp.  */
+  int_ftype_cstring_cstring_sizet
+    = build_function_type (integer_type_node,
+                          tree_cons (NULL_TREE, const_string_type_node,
+                                     tree_cons (NULL_TREE,
+                                                const_string_type_node,
+                                                sizetype_endlink)));
+
+  /* Prototype for strstr, strpbrk, etc.  */
+  string_ftype_string_string
+    = build_function_type (string_type_node,
+                          tree_cons (NULL_TREE, const_string_type_node,
+                                     tree_cons (NULL_TREE,
+                                                const_string_type_node,
+                                                endlink)));
+
+  /* Prototype for strchr.  */
+  string_ftype_string_int
+    = build_function_type (string_type_node,
+                          tree_cons (NULL_TREE, const_string_type_node,
+                                     tree_cons (NULL_TREE,
+                                                integer_type_node,
+                                                endlink)));
+
   /* Prototype for strlen.  */
   strlen_ftype
     = build_function_type (traditional_len_type_node,
                           tree_cons (NULL_TREE, const_string_type_node,
                                      endlink));
 
-  traditional_ptr_type_node = (flag_traditional && ! cplus_mode
+  traditional_ptr_type_node = ((flag_traditional && 
+                               c_language != clk_cplusplus)
                               ? string_type_node : ptr_type_node);
-  traditional_cptr_type_node = (flag_traditional && ! cplus_mode
+  traditional_cptr_type_node = ((flag_traditional && 
+                                c_language != clk_cplusplus)
                               ? const_string_type_node : const_ptr_type_node);
 
   /* Prototype for memcpy.  */
@@ -3519,6 +5151,18 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                                                 traditional_cptr_type_node,
                                                 traditional_len_endlink)));
 
+  /* Prototype for puts.  */
+  puts_ftype
+    = build_function_type (integer_type_node,
+                          tree_cons (NULL_TREE, const_string_type_node,
+                                     endlink));
+
+  /* Prototype for printf.  */
+  printf_ftype
+    = build_function_type (integer_type_node,
+                          tree_cons (NULL_TREE, const_string_type_node,
+                                     NULL_TREE));
+
   builtin_function ("__builtin_constant_p", default_function_type,
                    BUILT_IN_CONSTANT_P, BUILT_IN_NORMAL, NULL_PTR);
 
@@ -3534,7 +5178,7 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                    BUILT_IN_NORMAL, NULL_PTR);
   /* Define alloca, ffs as builtins.
      Declare _exit just to mark it as volatile.  */
-  if (! no_builtins && ! no_nonansi_builtins)
+  if (! flag_no_builtin && ! flag_no_nonansi_builtin)
     {
 #ifndef SMALL_STACK
       temp = builtin_function ("alloca", ptr_ftype_sizetype,
@@ -3553,6 +5197,12 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
       /* Suppress error if redefined as a non-function.  */
       DECL_BUILT_IN_NONANSI (temp) = 1;
 
+      temp = builtin_function ("index", string_ftype_string_int,
+                              BUILT_IN_INDEX, BUILT_IN_NORMAL, NULL_PTR);
+      DECL_BUILT_IN_NONANSI (temp) = 1;
+      temp = builtin_function ("rindex", string_ftype_string_int,
+                              BUILT_IN_RINDEX, BUILT_IN_NORMAL, NULL_PTR);
+      DECL_BUILT_IN_NONANSI (temp) = 1;
       /* The system prototypes for these functions have many
         variations, so don't specify parameters to avoid conflicts.
         The expand_* functions check the argument types anyway.  */
@@ -3574,6 +5224,10 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                    BUILT_IN_NORMAL, NULL_PTR);
   builtin_function ("__builtin_labs", long_ftype_long, BUILT_IN_LABS,
                    BUILT_IN_NORMAL, NULL_PTR);
+  builtin_function ("__builtin_llabs", longlong_ftype_longlong, BUILT_IN_LLABS,
+                   BUILT_IN_NORMAL, NULL_PTR);
+  builtin_function ("__builtin_imaxabs", intmax_ftype_intmax, BUILT_IN_IMAXABS,
+                   BUILT_IN_NORMAL, NULL_PTR);
   builtin_function ("__builtin_saveregs", ptr_ftype, BUILT_IN_SAVEREGS,
                    BUILT_IN_NORMAL, NULL_PTR);
   builtin_function ("__builtin_classify_type", default_function_type,
@@ -3681,10 +5335,28 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                    BUILT_IN_BZERO, BUILT_IN_NORMAL, "bzero");
   builtin_function ("__builtin_bcmp", bcmp_ftype,
                    BUILT_IN_BCMP, BUILT_IN_NORMAL, "bcmp");
-  builtin_function ("__builtin_strcmp", int_ftype_string_string,
-                   BUILT_IN_STRCMP, BUILT_IN_NORMAL, "strcmp");
+  builtin_function ("__builtin_index", string_ftype_string_int,
+                   BUILT_IN_INDEX, BUILT_IN_NORMAL, "index");
+  builtin_function ("__builtin_rindex", string_ftype_string_int,
+                   BUILT_IN_RINDEX, BUILT_IN_NORMAL, "rindex");
+  built_in_decls[BUILT_IN_STRCMP] =
+    builtin_function ("__builtin_strcmp", int_ftype_string_string,
+                     BUILT_IN_STRCMP, BUILT_IN_NORMAL, "strcmp");
+  builtin_function ("__builtin_strncmp", int_ftype_cstring_cstring_sizet,
+                   BUILT_IN_STRNCMP, BUILT_IN_NORMAL, "strncmp");
+  builtin_function ("__builtin_strstr", string_ftype_string_string,
+                   BUILT_IN_STRSTR, BUILT_IN_NORMAL, "strstr");
+  builtin_function ("__builtin_strpbrk", string_ftype_string_string,
+                   BUILT_IN_STRPBRK, BUILT_IN_NORMAL, "strpbrk");
+  built_in_decls[BUILT_IN_STRCHR] =
+    builtin_function ("__builtin_strchr", string_ftype_string_int,
+                   BUILT_IN_STRCHR, BUILT_IN_NORMAL, "strchr");
+  builtin_function ("__builtin_strrchr", string_ftype_string_int,
+                   BUILT_IN_STRRCHR, BUILT_IN_NORMAL, "strrchr");
   builtin_function ("__builtin_strcpy", string_ftype_ptr_ptr,
                    BUILT_IN_STRCPY, BUILT_IN_NORMAL, "strcpy");
+  builtin_function ("__builtin_strncpy", string_ftype_string_cstring_sizet,
+                   BUILT_IN_STRNCPY, BUILT_IN_NORMAL, "strncpy");
   builtin_function ("__builtin_strlen", strlen_ftype,
                    BUILT_IN_STRLEN, BUILT_IN_NORMAL, "strlen");
   builtin_function ("__builtin_sqrtf", float_ftype_float,
@@ -3705,8 +5377,30 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                    BUILT_IN_COS, BUILT_IN_NORMAL, "cos");
   builtin_function ("__builtin_cosl", ldouble_ftype_ldouble,
                    BUILT_IN_COS, BUILT_IN_NORMAL, "cosl");
-
-  if (! no_builtins)
+  built_in_decls[BUILT_IN_PUTCHAR] =
+    builtin_function ("__builtin_putchar", int_ftype_int,
+                     BUILT_IN_PUTCHAR, BUILT_IN_NORMAL, "putchar");
+  built_in_decls[BUILT_IN_PUTS] =
+    builtin_function ("__builtin_puts", puts_ftype,
+                     BUILT_IN_PUTS, BUILT_IN_NORMAL, "puts");
+  builtin_function ("__builtin_printf", printf_ftype,
+                   BUILT_IN_PRINTF, BUILT_IN_FRONTEND, "printf");
+  /* We declare these without argument so that the initial declaration
+     for these identifiers is a builtin.  That allows us to redeclare
+     them later with argument without worrying about the explicit
+     declarations in stdio.h being taken as the initial declaration.
+     Also, save the _DECL for these so we can use them later.  */
+  built_in_decls[BUILT_IN_FWRITE] =
+    builtin_function ("__builtin_fwrite", sizet_ftype_any,
+                     BUILT_IN_FWRITE, BUILT_IN_NORMAL, "fwrite");
+  built_in_decls[BUILT_IN_FPUTC] =
+    builtin_function ("__builtin_fputc", int_ftype_any,
+                     BUILT_IN_FPUTC, BUILT_IN_NORMAL, "fputc");
+  built_in_decls[BUILT_IN_FPUTS] =
+    builtin_function ("__builtin_fputs", int_ftype_any,
+                     BUILT_IN_FPUTS, BUILT_IN_NORMAL, "fputs");
+
+  if (! flag_no_builtin)
     {
       builtin_function ("abs", int_ftype_int, BUILT_IN_ABS,
                        BUILT_IN_NORMAL, NULL_PTR);
@@ -3718,6 +5412,13 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                        BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("labs", long_ftype_long, BUILT_IN_LABS,
                        BUILT_IN_NORMAL, NULL_PTR);
+      if (flag_isoc99 || ! flag_no_nonansi_builtin)
+       {
+         builtin_function ("llabs", longlong_ftype_longlong, BUILT_IN_LLABS,
+                           BUILT_IN_NORMAL, NULL_PTR);
+         builtin_function ("imaxabs", intmax_ftype_intmax, BUILT_IN_IMAXABS,
+                           BUILT_IN_NORMAL, NULL_PTR);
+       }
       builtin_function ("memcpy", memcpy_ftype, BUILT_IN_MEMCPY,
                        BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("memcmp", int_ftype_cptr_cptr_sizet, BUILT_IN_MEMCMP,
@@ -3726,8 +5427,20 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                        BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("strcmp", int_ftype_string_string, BUILT_IN_STRCMP,
                        BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strncmp", int_ftype_cstring_cstring_sizet,
+                       BUILT_IN_STRNCMP, BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strstr", string_ftype_string_string, BUILT_IN_STRSTR,
+                       BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strchr", string_ftype_string_int, BUILT_IN_STRCHR,
+                       BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strrchr", string_ftype_string_int, BUILT_IN_STRRCHR,
+                       BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strpbrk", string_ftype_string_string, BUILT_IN_STRPBRK,
+                       BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("strcpy", string_ftype_ptr_ptr, BUILT_IN_STRCPY,
                        BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("strncpy", string_ftype_string_cstring_sizet,
+                       BUILT_IN_STRNCPY, BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("strlen", strlen_ftype, BUILT_IN_STRLEN,
                        BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("sqrtf", float_ftype_float, BUILT_IN_FSQRT,
@@ -3748,30 +5461,33 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                        BUILT_IN_NORMAL, NULL_PTR);
       builtin_function ("cosl", ldouble_ftype_ldouble, BUILT_IN_COS,
                        BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("printf", printf_ftype, BUILT_IN_PRINTF,
+                       BUILT_IN_FRONTEND, NULL_PTR);
+      /* We declare these without argument so that the initial
+         declaration for these identifiers is a builtin.  That allows
+         us to redeclare them later with argument without worrying
+         about the explicit declarations in stdio.h being taken as the
+         initial declaration.  */
+      builtin_function ("fputc", int_ftype_any, BUILT_IN_FPUTC,
+                       BUILT_IN_NORMAL, NULL_PTR);
+      builtin_function ("fputs", int_ftype_any, BUILT_IN_FPUTS,
+                       BUILT_IN_NORMAL, NULL_PTR);
 
       /* Declare these functions volatile
         to avoid spurious "control drops through" warnings.  */
-      temp = builtin_function ("abort", cplus_mode ? void_ftype : void_ftype_any,
+      temp = builtin_function ("abort", 
+                              ((c_language == clk_cplusplus)
+                               ? void_ftype : void_ftype_any),
                               0, NOT_BUILT_IN, NULL_PTR);
       TREE_THIS_VOLATILE (temp) = 1;
       TREE_SIDE_EFFECTS (temp) = 1;
 
-#if 0 /* ??? The C++ frontend used to do this.  */
-      /* Well, these are actually ANSI, but we can't set DECL_BUILT_IN on
-        them...  */
-      DECL_BUILT_IN_NONANSI (temp) = 1;
-#endif
       temp = builtin_function ("exit",
-                              cplus_mode ? void_ftype_int : void_ftype_any,
+                              ((c_language == clk_cplusplus)
+                               ? void_ftype_int : void_ftype_any),
                               0, NOT_BUILT_IN, NULL_PTR);
       TREE_THIS_VOLATILE (temp) = 1;
       TREE_SIDE_EFFECTS (temp) = 1;
-
-#if 0 /* ??? The C++ frontend used to do this.  */
-      /* Well, these are actually ANSI, but we can't set DECL_BUILT_IN on
-        them...  */
-      DECL_BUILT_IN_NONANSI (temp) = 1;
-#endif
     }
 
 #if 0
@@ -3795,6 +5511,8 @@ c_common_nodes_and_builtins (cplus_mode, no_builtins, no_nonansi_builtins)
                    BUILT_IN_NORMAL, NULL_PTR);
 #endif
 
+  main_identifier_node = get_identifier ("main");
+
   /* ??? Perhaps there's a better place to do this.  But it is related
      to __builtin_va_arg, so it isn't that off-the-wall.  */
   lang_type_promotes_to = simple_type_promotes_to;
@@ -3862,6 +5580,19 @@ self_promoting_args_p (parms)
   return 1;
 }
 
+/* Recursively examines the array elements of TYPE, until a non-array
+   element type is found.  */
+
+tree
+strip_array_types (type)
+     tree type;
+{
+  while (TREE_CODE (type) == ARRAY_TYPE)
+    type = TREE_TYPE (type);
+
+  return type;
+}
+
 /* Recognize certain built-in functions so we can make tree-codes
    other than CALL_EXPR.  We do this when it enables fold-const.c
    to do something useful.  */
@@ -3883,6 +5614,8 @@ expand_tree_builtin (function, params, coerced_params)
     {
     case BUILT_IN_ABS:
     case BUILT_IN_LABS:
+    case BUILT_IN_LLABS:
+    case BUILT_IN_IMAXABS:
     case BUILT_IN_FABS:
       if (coerced_params == 0)
        return integer_zero_node;
@@ -3963,6 +5696,420 @@ expand_tree_builtin (function, params, coerced_params)
   return NULL_TREE;
 }
 
+/* Returns non-zero if CODE is the code for a statement.  */
+
+int
+statement_code_p (code)
+     enum tree_code code;
+{
+  switch (code)
+    {
+    case EXPR_STMT:
+    case COMPOUND_STMT:
+    case DECL_STMT:
+    case IF_STMT:
+    case FOR_STMT:
+    case WHILE_STMT:
+    case DO_STMT:
+    case RETURN_STMT:
+    case BREAK_STMT:
+    case CONTINUE_STMT:
+    case SCOPE_STMT:
+    case SWITCH_STMT:
+    case GOTO_STMT:
+    case LABEL_STMT:
+    case ASM_STMT:
+    case CASE_LABEL:
+      return 1;
+
+    default:
+      if (lang_statement_code_p)
+       return (*lang_statement_code_p) (code);
+      return 0;
+    }
+}
+
+/* Walk the statemen tree, rooted at *tp.  Apply FUNC to all the
+   sub-trees of *TP in a pre-order traversal.  FUNC is called with the
+   DATA and the address of each sub-tree.  If FUNC returns a non-NULL
+   value, the traversal is aborted, and the value returned by FUNC is
+   returned.  If FUNC sets WALK_SUBTREES to zero, then the subtrees of
+   the node being visited are not walked.
+
+   We don't need a without_duplicates variant of this one because the
+   statement tree is a tree, not a graph.  */
+
+tree 
+walk_stmt_tree (tp, func, data)
+     tree *tp;
+     walk_tree_fn func;
+     void *data;
+{
+  enum tree_code code;
+  int walk_subtrees;
+  tree result;
+  int i, len;
+
+#define WALK_SUBTREE(NODE)                             \
+  do                                                   \
+    {                                                  \
+      result = walk_stmt_tree (&(NODE), func, data);   \
+      if (result)                                      \
+       return result;                                  \
+    }                                                  \
+  while (0)
+
+  /* Skip empty subtrees.  */
+  if (!*tp)
+    return NULL_TREE;
+
+  /* Skip subtrees below non-statement nodes.  */
+  if (!statement_code_p (TREE_CODE (*tp)))
+    return NULL_TREE;
+
+  /* Call the function.  */
+  walk_subtrees = 1;
+  result = (*func) (tp, &walk_subtrees, data);
+
+  /* If we found something, return it.  */
+  if (result)
+    return result;
+
+  /* Even if we didn't, FUNC may have decided that there was nothing
+     interesting below this point in the tree.  */
+  if (!walk_subtrees)
+    return NULL_TREE;
+
+  /* FUNC may have modified the tree, recheck that we're looking at a
+     statement node.  */
+  code = TREE_CODE (*tp);
+  if (!statement_code_p (code))
+    return NULL_TREE;
+
+  /* Walk over all the sub-trees of this operand.  Statement nodes never
+     contain RTL, and we needn't worry about TARGET_EXPRs.  */
+  len = TREE_CODE_LENGTH (code);
+
+  /* Go through the subtrees.  We need to do this in forward order so
+     that the scope of a FOR_EXPR is handled properly.  */
+  for (i = 0; i < len; ++i)
+    WALK_SUBTREE (TREE_OPERAND (*tp, i));
+
+  /* Finally visit the chain.  This can be tail-recursion optimized if
+     we write it this way.  */
+  return walk_stmt_tree (&TREE_CHAIN (*tp), func, data);
+
+#undef WALK_SUBTREE
+}
+
+/* Used to compare case labels.  K1 and K2 are actually tree nodes
+   representing case labels, or NULL_TREE for a `default' label.
+   Returns -1 if K1 is ordered before K2, -1 if K1 is ordered after
+   K2, and 0 if K1 and K2 are equal.  */
+
+int
+case_compare (k1, k2)
+     splay_tree_key k1;
+     splay_tree_key k2;
+{
+  /* Consider a NULL key (such as arises with a `default' label) to be
+     smaller than anything else.  */
+  if (!k1)
+    return k2 ? -1 : 0;
+  else if (!k2)
+    return k1 ? 1 : 0;
+
+  return tree_int_cst_compare ((tree) k1, (tree) k2);
+}
+
+/* Process a case label for the range LOW_VALUE ... HIGH_VALUE.  If
+   LOW_VALUE and HIGH_VALUE are both NULL_TREE then this case label is
+   actually a `default' label.  If only HIGH_VALUE is NULL_TREE, then
+   case label was declared using the usual C/C++ syntax, rather than
+   the GNU case range extension.  CASES is a tree containing all the
+   case ranges processed so far; COND is the condition for the
+   switch-statement itself.  Returns the CASE_LABEL created, or
+   ERROR_MARK_NODE if no CASE_LABEL is created.  */
+
+tree
+c_add_case_label (cases, cond, low_value, high_value)
+     splay_tree cases;
+     tree cond;
+     tree low_value;
+     tree high_value;
+{
+  tree type;
+  tree label;
+  tree case_label;
+  splay_tree_node node;
+
+  /* Create the LABEL_DECL itself.  */
+  label = build_decl (LABEL_DECL, NULL_TREE, NULL_TREE);
+  DECL_CONTEXT (label) = current_function_decl;
+
+  /* If there was an error processing the switch condition, bail now
+     before we get more confused.  */
+  if (!cond || cond == error_mark_node)
+    {
+      /* Add a label anyhow so that the back-end doesn't think that
+        the beginning of the switch is unreachable.  */
+      if (!cases->root)
+       add_stmt (build_case_label (NULL_TREE, NULL_TREE, label));
+      return error_mark_node;
+    }
+
+  if ((low_value && TREE_TYPE (low_value) 
+       && POINTER_TYPE_P (TREE_TYPE (low_value))) 
+      || (high_value && TREE_TYPE (high_value)
+         && POINTER_TYPE_P (TREE_TYPE (high_value))))
+    error ("pointers are not permitted as case values");
+
+  /* Case ranges are a GNU extension.  */
+  if (high_value && pedantic)
+    {
+      if (c_language == clk_cplusplus)
+       pedwarn ("ISO C++ forbids range expressions in switch statements");
+      else
+       pedwarn ("ISO C forbids range expressions in switch statements");
+    }
+
+  type = TREE_TYPE (cond);
+  if (low_value)
+    {
+      low_value = check_case_value (low_value);
+      low_value = convert_and_check (type, low_value);
+    }
+  if (high_value)
+    {
+      high_value = check_case_value (high_value);
+      high_value = convert_and_check (type, high_value);
+    }
+
+  /* If an error has occurred, bail out now.  */
+  if (low_value == error_mark_node || high_value == error_mark_node)
+    {
+      if (!cases->root)
+       add_stmt (build_case_label (NULL_TREE, NULL_TREE, label));
+      return error_mark_node;
+    }
+
+  /* If the LOW_VALUE and HIGH_VALUE are the same, then this isn't
+     really a case range, even though it was written that way.  Remove
+     the HIGH_VALUE to simplify later processing.  */
+  if (tree_int_cst_equal (low_value, high_value))
+    high_value = NULL_TREE;
+  if (low_value && high_value 
+      && !tree_int_cst_lt (low_value, high_value)) 
+    warning ("empty range specified");
+
+  /* Look up the LOW_VALUE in the table of case labels we already
+     have.  */
+  node = splay_tree_lookup (cases, (splay_tree_key) low_value);
+  /* If there was not an exact match, check for overlapping ranges.
+     There's no need to do this if there's no LOW_VALUE or HIGH_VALUE;
+     that's a `default' label and the only overlap is an exact match.  */
+  if (!node && (low_value || high_value))
+    {
+      splay_tree_node low_bound;
+      splay_tree_node high_bound;
+
+      /* Even though there wasn't an exact match, there might be an
+        overlap between this case range and another case range.
+        Since we've (inductively) not allowed any overlapping case
+        ranges, we simply need to find the greatest low case label
+        that is smaller that LOW_VALUE, and the smallest low case
+        label that is greater than LOW_VALUE.  If there is an overlap
+        it will occur in one of these two ranges.  */
+      low_bound = splay_tree_predecessor (cases,
+                                         (splay_tree_key) low_value);
+      high_bound = splay_tree_successor (cases,
+                                        (splay_tree_key) low_value);
+
+      /* Check to see if the LOW_BOUND overlaps.  It is smaller than
+        the LOW_VALUE, so there is no need to check unless the
+        LOW_BOUND is in fact itself a case range.  */
+      if (low_bound
+         && CASE_HIGH ((tree) low_bound->value)
+         && tree_int_cst_compare (CASE_HIGH ((tree) low_bound->value),
+                                   low_value) >= 0)
+       node = low_bound;
+      /* Check to see if the HIGH_BOUND overlaps.  The low end of that
+        range is bigger than the low end of the current range, so we
+        are only interested if the current range is a real range, and
+        not an ordinary case label.  */
+      else if (high_bound 
+              && high_value
+              && (tree_int_cst_compare ((tree) high_bound->key,
+                                        high_value)
+                  <= 0))
+       node = high_bound;
+    }
+  /* If there was an overlap, issue an error.  */
+  if (node)
+    {
+      tree duplicate = CASE_LABEL_DECL ((tree) node->value);
+
+      if (high_value)
+       {
+         error ("duplicate (or overlapping) case value");
+         error_with_decl (duplicate, 
+                          "this is the first entry overlapping that value");
+       }
+      else if (low_value)
+       {
+         error ("duplicate case value") ;
+         error_with_decl (duplicate, "previously used here");
+       }
+      else
+       {
+         error ("multiple default labels in one switch");
+         error_with_decl (duplicate, "this is the first default label");
+       }
+      if (!cases->root)
+       add_stmt (build_case_label (NULL_TREE, NULL_TREE, label));
+    }
+
+  /* Add a CASE_LABEL to the statement-tree.  */
+  case_label = add_stmt (build_case_label (low_value, high_value, label));
+  /* Register this case label in the splay tree.  */
+  splay_tree_insert (cases, 
+                    (splay_tree_key) low_value,
+                    (splay_tree_value) case_label);
+
+  return case_label;
+}
+
+/* Mark P (a stmt_tree) for GC.  The use of a `void *' for the
+   parameter allows this function to be used as a GC-marking
+   function.  */
+
+void
+mark_stmt_tree (p)
+     void *p;
+{
+  stmt_tree st = (stmt_tree) p;
+
+  ggc_mark_tree (st->x_last_stmt);
+  ggc_mark_tree (st->x_last_expr_type);
+}
+
+/* Mark LD for GC.  */
+
+void
+c_mark_lang_decl (c)
+     struct c_lang_decl *c;
+{
+  ggc_mark_tree (c->saved_tree);
+}
+
+/* Mark F for GC.  */
+
+void
+mark_c_language_function (f)
+     struct language_function *f;
+{
+  if (!f)
+    return;
+
+  mark_stmt_tree (&f->x_stmt_tree);
+  ggc_mark_tree (f->x_scope_stmt_stack);
+}
+
+/* Hook used by expand_expr to expand language-specific tree codes.  */
+
+rtx
+c_expand_expr (exp, target, tmode, modifier)
+     tree exp;
+     rtx target;
+     enum machine_mode tmode;
+     enum expand_modifier modifier;
+{
+  switch (TREE_CODE (exp))
+    {
+    case STMT_EXPR:
+      {
+       tree rtl_expr;
+       rtx result;
+
+       /* Since expand_expr_stmt calls free_temp_slots after every
+          expression statement, we must call push_temp_slots here.
+          Otherwise, any temporaries in use now would be considered
+          out-of-scope after the first EXPR_STMT from within the
+          STMT_EXPR.  */
+       push_temp_slots ();
+       rtl_expr = expand_start_stmt_expr ();
+       expand_stmt (STMT_EXPR_STMT (exp));
+       expand_end_stmt_expr (rtl_expr);
+       result = expand_expr (rtl_expr, target, tmode, modifier);
+       pop_temp_slots ();
+       return result;
+      }
+      break;
+      
+    case CALL_EXPR:
+      {
+       if (TREE_CODE (TREE_OPERAND (exp, 0)) == ADDR_EXPR
+           && (TREE_CODE (TREE_OPERAND (TREE_OPERAND (exp, 0), 0))
+               == FUNCTION_DECL)
+           && DECL_BUILT_IN (TREE_OPERAND (TREE_OPERAND (exp, 0), 0))
+           && (DECL_BUILT_IN_CLASS (TREE_OPERAND (TREE_OPERAND (exp, 0), 0))
+               == BUILT_IN_FRONTEND))
+         return c_expand_builtin (exp, target, tmode, modifier);
+       else
+         abort();
+      }
+      break;
+
+    default:
+      abort ();
+    }
+
+  abort ();
+  return NULL;
+}
+
+/* Hook used by safe_from_p to handle language-specific tree codes.  */
+
+int
+c_safe_from_p (target, exp)
+     rtx target;
+     tree exp;
+{
+  /* We can see statements here when processing the body of a
+     statement-expression.  For a declaration statement declaring a
+     variable, look at the variable's initializer.  */
+  if (TREE_CODE (exp) == DECL_STMT) 
+    {
+      tree decl = DECL_STMT_DECL (exp);
+
+      if (TREE_CODE (decl) == VAR_DECL
+         && DECL_INITIAL (decl)
+         && !safe_from_p (target, DECL_INITIAL (decl), /*top_p=*/0))
+       return 0;
+    }
+
+  /* For any statement, we must follow the statement-chain.  */
+  if (statement_code_p (TREE_CODE (exp)) && TREE_CHAIN (exp))
+    return safe_from_p (target, TREE_CHAIN (exp), /*top_p=*/0);
+
+  /* Assume everything else is safe.  */
+  return 1;
+}
+
+/* Hook used by unsafe_for_reeval to handle language-specific tree codes.  */
+
+int
+c_unsafe_for_reeval (exp)
+     tree exp;
+{
+  /* Statement expressions may not be reevaluated.  */
+  if (TREE_CODE (exp) == STMT_EXPR)
+    return 2;
+
+  /* Walk all other expressions.  */
+  return -1;
+}
+
 /* Tree code classes. */
 
 #define DEFTREECODE(SYM, NAME, TYPE, LENGTH) TYPE,
@@ -4010,4 +6157,217 @@ add_c_tree_codes ()
   memcpy (tree_code_name + (int) LAST_AND_UNUSED_TREE_CODE,
          c_tree_code_name,
          (LAST_C_TREE_CODE - (int)LAST_AND_UNUSED_TREE_CODE) * sizeof (char *));
+  lang_unsafe_for_reeval = c_unsafe_for_reeval;
+}
+
+#define CALLED_AS_BUILT_IN(NODE) \
+   (!strncmp (IDENTIFIER_POINTER (DECL_NAME (NODE)), "__builtin_", 10))
+
+static rtx
+c_expand_builtin (exp, target, tmode, modifier)
+     tree exp;
+     rtx target;
+     enum machine_mode tmode;
+     enum expand_modifier modifier;
+{
+  tree type = TREE_TYPE (exp);
+  tree fndecl = TREE_OPERAND (TREE_OPERAND (exp, 0), 0);
+  tree arglist = TREE_OPERAND (exp, 1);
+  enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl);
+  enum tree_code code = TREE_CODE (exp);
+  const int ignore = (target == const0_rtx
+                     || ((code == NON_LVALUE_EXPR || code == NOP_EXPR
+                          || code == CONVERT_EXPR || code == REFERENCE_EXPR
+                          || code == COND_EXPR)
+                         && TREE_CODE (type) == VOID_TYPE));
+
+  if (! optimize && ! CALLED_AS_BUILT_IN (fndecl))
+    return expand_call (exp, target, ignore);
+
+  switch (fcode)
+    {
+    case BUILT_IN_PRINTF:
+      target = c_expand_builtin_printf (arglist, target, tmode,
+                                       modifier, ignore);
+      if (target)
+       return target;
+      break;
+
+    default:                   /* just do library call, if unknown builtin */
+      error ("built-in function `%s' not currently supported",
+            IDENTIFIER_POINTER (DECL_NAME (fndecl)));
+    }
+
+  /* The switch statement above can drop through to cause the function
+     to be called normally.  */
+  return expand_call (exp, target, ignore);
+}
+
+/* Check an arglist to *printf for problems.  The arglist should start
+   at the format specifier, with the remaining arguments immediately
+   following it. */
+static int
+is_valid_printf_arglist (arglist)
+  tree arglist;
+{
+  /* Save this value so we can restore it later. */
+  const int SAVE_pedantic = pedantic;
+  int diagnostic_occurred = 0;
+
+  /* Set this to a known value so the user setting won't affect code
+     generation.  */
+  pedantic = 1;
+  /* Check to make sure there are no format specifier errors. */
+  check_function_format (&diagnostic_occurred,
+                        maybe_get_identifier("printf"),
+                        NULL_TREE, arglist);
+
+  /* Restore the value of `pedantic'. */
+  pedantic = SAVE_pedantic;
+
+  /* If calling `check_function_format_ptr' produces a warning, we
+     return false, otherwise we return true. */
+  return ! diagnostic_occurred;
+}
+
+/* If the arguments passed to printf are suitable for optimizations,
+   we attempt to transform the call. */
+static rtx
+c_expand_builtin_printf (arglist, target, tmode, modifier, ignore)
+     tree arglist;
+     rtx target;
+     enum machine_mode tmode;
+     enum expand_modifier modifier;
+     int ignore;
+{
+  tree fn_putchar = built_in_decls[BUILT_IN_PUTCHAR],
+    fn_puts = built_in_decls[BUILT_IN_PUTS];
+  tree fn, format_arg, stripped_string;
+
+  /* If the return value is used, or the replacement _DECL isn't
+     initialized, don't do the transformation. */
+  if (!ignore || !fn_putchar || !fn_puts)
+    return 0;
+
+  /* Verify the required arguments in the original call. */
+  if (arglist == 0
+      || (TREE_CODE (TREE_TYPE (TREE_VALUE (arglist))) != POINTER_TYPE))
+    return 0;
+  
+  /* Check the specifier vs. the parameters. */
+  if (!is_valid_printf_arglist (arglist))
+    return 0;
+  
+  format_arg = TREE_VALUE (arglist);
+  stripped_string = format_arg;
+  STRIP_NOPS (stripped_string);
+  if (stripped_string && TREE_CODE (stripped_string) == ADDR_EXPR)
+    stripped_string = TREE_OPERAND (stripped_string, 0);
+
+  /* If the format specifier isn't a STRING_CST, punt.  */
+  if (TREE_CODE (stripped_string) != STRING_CST)
+    return 0;
+  
+  /* OK!  We can attempt optimization.  */
+
+  /* If the format specifier was "%s\n", call __builtin_puts(arg2). */
+  if (strcmp (TREE_STRING_POINTER (stripped_string), "%s\n") == 0)
+    {
+      arglist = TREE_CHAIN (arglist);
+      fn = fn_puts;
+    }
+  /* If the format specifier was "%c", call __builtin_putchar (arg2). */
+  else if (strcmp (TREE_STRING_POINTER (stripped_string), "%c") == 0)
+    {
+      arglist = TREE_CHAIN (arglist);
+      fn = fn_putchar;
+    }
+  else
+    {
+     /* We can't handle anything else with % args or %% ... yet. */
+      if (strchr (TREE_STRING_POINTER (stripped_string), '%'))
+       return 0;
+      
+      /* If the resulting constant string has a length of 1, call
+         putchar.  Note, TREE_STRING_LENGTH includes the terminating
+         NULL in its count.  */
+      if (TREE_STRING_LENGTH (stripped_string) == 2)
+        {
+         /* Given printf("c"), (where c is any one character,)
+             convert "c"[0] to an int and pass that to the replacement
+             function. */
+         arglist = build_int_2 (TREE_STRING_POINTER (stripped_string)[0], 0);
+         arglist = build_tree_list (NULL_TREE, arglist);
+         
+         fn = fn_putchar;
+        }
+      /* If the resulting constant was "string\n", call
+         __builtin_puts("string").  Ensure "string" has at least one
+         character besides the trailing \n.  Note, TREE_STRING_LENGTH
+         includes the terminating NULL in its count.  */
+      else if (TREE_STRING_LENGTH (stripped_string) > 2
+              && TREE_STRING_POINTER (stripped_string)
+              [TREE_STRING_LENGTH (stripped_string) - 2] == '\n')
+        {
+         /* Create a NULL-terminated string that's one char shorter
+            than the original, stripping off the trailing '\n'.  */
+         const int newlen = TREE_STRING_LENGTH (stripped_string) - 1;
+         char *newstr = (char *) alloca (newlen);
+         memcpy (newstr, TREE_STRING_POINTER (stripped_string), newlen - 1);
+         newstr[newlen - 1] = 0;
+         
+         arglist = combine_strings (build_string (newlen, newstr));
+         arglist = build_tree_list (NULL_TREE, arglist);
+         fn = fn_puts;
+       }
+      else
+       /* We'd like to arrange to call fputs(string) here, but we
+           need stdout and don't have a way to get it ... yet.  */
+       return 0;
+    }
+  
+  return expand_expr (build_function_call (fn, arglist),
+                     (ignore ? const0_rtx : target),
+                     tmode, modifier);
+}
+\f
+
+/* Given a boolean expression ARG, return a tree representing an increment
+   or decrement (as indicated by CODE) of ARG.  The front end must check for
+   invalid cases (e.g., decrement in C++).  */
+tree
+boolean_increment (code, arg)
+     enum tree_code code;
+     tree arg;
+{
+  tree val;
+  tree true_res = (c_language == clk_cplusplus
+                  ? boolean_true_node
+                  : c_bool_true_node);
+  arg = stabilize_reference (arg);
+  switch (code)
+    {
+    case PREINCREMENT_EXPR:
+      val = build (MODIFY_EXPR, TREE_TYPE (arg), arg, true_res);
+      break;
+    case POSTINCREMENT_EXPR:
+      val = build (MODIFY_EXPR, TREE_TYPE (arg), arg, true_res);
+      arg = save_expr (arg);
+      val = build (COMPOUND_EXPR, TREE_TYPE (arg), val, arg);
+      val = build (COMPOUND_EXPR, TREE_TYPE (arg), arg, val);
+      break;
+    case PREDECREMENT_EXPR:
+      val = build (MODIFY_EXPR, TREE_TYPE (arg), arg, invert_truthvalue (arg));
+      break;
+    case POSTDECREMENT_EXPR:
+      val = build (MODIFY_EXPR, TREE_TYPE (arg), arg, invert_truthvalue (arg));
+      arg = save_expr (arg);
+      val = build (COMPOUND_EXPR, TREE_TYPE (arg), val, arg);
+      val = build (COMPOUND_EXPR, TREE_TYPE (arg), arg, val);
+      break;
+    default:
+      abort ();
+    }
+  TREE_SIDE_EFFECTS (val) = 1;
+  return val;
 }