This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC PATCH] __builtin_va_arg_pack ()


Hi!

As I already wrote in
http://gcc.gnu.org/ml/gcc-patches/2007-07/msg01033.html
Alex's __attribute__((__gnu_inline__)) support for C++ allows glibc
to implement most of the -D_FORTIFY_SOURCE fortification even for C++,
with minimal changes, but there is a notable exception that *printf
family of functions can't be protected that way.
The problem is that these functions have variable number of arguments
and for fortification we need to write trivial wrappers for them
which can be inlined.
For C we currently define these as macros, which has issues on its own,
although it is permitted by POSIX, if using a function pointer
with the same name as one of the functions one has to prevent
expanding them as function-like macros, so say
#include <stdio.h>
...
 foo->printf (a, b, c);
won't compile while
 (foo->printf) (a, b, c);
will.  For C++ we can't use function-like macros here, because
one can use the same identifier in his own namespace for different
purpose.

The following patch allows using inline functions for those cases,
by introducing a __builtin_va_arg_pack () builtin which during
inlining expands to all anonymous arguments of the inline function.
It is similar to how __VA_ARGS__ can be used in the preprocessor,
with a few limitations.
Using it in an out-of-line function is an error, it doesn't make sense
in that case, so it should be only used in always_inline inlines
or gnu_inline extern inline functions (the latter, if it can't be inlined,
will just result in an external call and that external implementation
obviously can't use __builtin_va_arg_pack (), but the inline function
can).  Also, it can be only used in the context of anonymous arguments
of another variable number of arguments call.

What do you think about this?

2007-08-30  Jakub Jelinek  <jakub@redhat.com>

	* builtins.def (BUILT_IN_VA_ARG_PACK): New built-in.
	* tree.h (CALL_EXPR_VA_ARG_PACK): Define.
	* tree-inline.h (copy_body_data): Add call_expr field.
	* tree-inline.c (expand_call_inline): Initialize call_expr.
	(copy_bb): Append anonymous inline fn arguments to arguments
	when inlining a CALL_EXPR_VA_ARG_PACK call.
	* builtins.c (expand_builtin): Issue an error if
	BUILT_IN_VA_ARG_PACK is seen during expand.
	(fold_call_expr, fold_builtin_call_array): Don't fold
	CALL_EXPR_VA_ARG_PACK CALL_EXPRs or calls with
	__builtin_va_arg_pack () call as last argument.
	* gimplify.c (gimplify_call_expr): If last argument to a vararg
	function is __builtin_va_arg_pack (), decrease number of call
	arguments and instead set CALL_EXPR_VA_ARG_PACK on the CALL_EXPR.
	* expr.c (expand_expr_real_1): Issue an error if
	CALL_EXPR_VA_ARG_PACK CALL_EXPR is seen during expand.
	* tree-pretty-print.c (dump_generic_node): Handle printing
	CALL_EXPR_VA_ARG_PACK bit on CALL_EXPRs.
	* doc/extend.texi (__builtin_va_arg_pack): Document.

	* gcc.c-torture/execute/va-arg-pack-1.c: New test.
	* gcc.dg/va-arg-pack-1.c: New test.

--- gcc/builtins.def.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/builtins.def	2007-08-30 18:46:59.000000000 +0200
@@ -701,6 +701,7 @@ DEF_GCC_BUILTIN        (BUILT_IN_UPDATE_
 DEF_GCC_BUILTIN        (BUILT_IN_VA_COPY, "va_copy", BT_FN_VOID_VALIST_REF_VALIST_ARG, ATTR_NULL)
 DEF_GCC_BUILTIN        (BUILT_IN_VA_END, "va_end", BT_FN_VOID_VALIST_REF, ATTR_NULL)
 DEF_GCC_BUILTIN        (BUILT_IN_VA_START, "va_start", BT_FN_VOID_VALIST_REF_VAR, ATTR_NULL)
+DEF_GCC_BUILTIN        (BUILT_IN_VA_ARG_PACK, "va_arg_pack", BT_FN_INT, ATTR_PURE_NOTHROW_LIST)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN__EXIT, "_exit", BT_FN_VOID_INT, ATTR_NORETURN_NOTHROW_LIST)
 DEF_C99_BUILTIN        (BUILT_IN__EXIT2, "_Exit", BT_FN_VOID_INT, ATTR_NORETURN_NOTHROW_LIST)
 
--- gcc/tree.h.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/tree.h	2007-08-30 18:46:59.000000000 +0200
@@ -464,6 +464,8 @@ struct gimple_stmt GTY(())
            VAR_DECL or FUNCTION_DECL or IDENTIFIER_NODE
        ASM_VOLATILE_P in
            ASM_EXPR
+       CALL_EXPR_VA_ARG_PACK in
+	  CALL_EXPR
        TYPE_CACHED_VALUES_P in
           ..._TYPE
        SAVE_EXPR_RESOLVED_P in
@@ -1222,6 +1224,11 @@ extern void omp_clause_range_check_faile
 #define SAVE_EXPR_RESOLVED_P(NODE) \
   (TREE_CHECK (NODE, SAVE_EXPR)->base.public_flag)
 
+/* Set on a CALL_EXPR if this stdarg call should be passed the argument
+   pack.  */
+#define CALL_EXPR_VA_ARG_PACK(NODE) \
+  (CALL_EXPR_CHECK(NODE)->base.public_flag)
+
 /* In any expression, decl, or constant, nonzero means it has side effects or
    reevaluation of the whole expression could produce a different value.
    This is set if any subexpression is a function call, a side effect or a
--- gcc/tree-inline.h.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/tree-inline.h	2007-08-30 18:46:59.000000000 +0200
@@ -56,6 +56,10 @@ typedef struct copy_body_data
   /* Current BLOCK.  */
   tree block;
 
+  /* CALL_EXPR if va arg parameter packs should be expanded or NULL
+     ig not.  */
+  tree call_expr;
+
   /* Exception region the inlined call lie in.  */
   int eh_region;
   /* Take region number in the function being copied, add this value and
--- gcc/tree-inline.c.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/tree-inline.c	2007-08-30 18:46:59.000000000 +0200
@@ -815,9 +815,53 @@ copy_bb (copy_body_data *id, basic_block
 	     into multiple statements, we need to process all of them.  */
 	  while (!bsi_end_p (copy_bsi))
 	    {
-	      stmt = bsi_stmt (copy_bsi);
+	      tree *stmtp = bsi_stmt_ptr (copy_bsi);
+	      tree stmt = *stmtp;
 	      call = get_call_expr_in (stmt);
 
+	      if (call && CALL_EXPR_VA_ARG_PACK (call) && id->call_expr)
+		{
+		  /* __builtin_va_arg_pack () should be replaced by
+		     all arguments corresponding to ... in the caller.  */
+		  tree p, *argarray, new_call, *call_ptr;
+		  int nargs = call_expr_nargs (id->call_expr);
+
+		  for (p = DECL_ARGUMENTS (id->src_fn); p; p = TREE_CHAIN (p))
+		    nargs--;
+
+		  argarray = (tree *) alloca ((nargs + call_expr_nargs (call))
+					      * sizeof (tree));
+
+		  memcpy (argarray, CALL_EXPR_ARGP (call),
+			  call_expr_nargs (call) * sizeof (*argarray));
+		  memcpy (argarray + call_expr_nargs (call),
+			  CALL_EXPR_ARGP (id->call_expr)
+			  + (call_expr_nargs (id->call_expr) - nargs),
+			  nargs * sizeof (*argarray));
+
+		  new_call = build_call_array (TREE_TYPE (call),
+					       CALL_EXPR_FN (call),
+					       nargs + call_expr_nargs (call),
+					       argarray);
+		  CALL_EXPR_STATIC_CHAIN (new_call)
+		    = CALL_EXPR_STATIC_CHAIN (call);
+		  CALL_EXPR_TAILCALL (new_call) = CALL_EXPR_TAILCALL (call);
+		  CALL_EXPR_RETURN_SLOT_OPT (new_call)
+		    = CALL_EXPR_RETURN_SLOT_OPT (call);
+		  SET_EXPR_LOCUS (new_call, EXPR_LOCUS (call));
+		  TREE_BLOCK (new_call) = TREE_BLOCK (call);
+
+		  call_ptr = stmtp;
+		  if (TREE_CODE (*call_ptr) == GIMPLE_MODIFY_STMT)
+		    call_ptr = &GIMPLE_STMT_OPERAND (*call_ptr, 1);
+		  if (TREE_CODE (*call_ptr) == WITH_SIZE_EXPR)
+		    call_ptr = &TREE_OPERAND (*call_ptr, 0);
+		  gcc_assert (*call_ptr == call);
+		  *call_ptr = new_call;
+		  stmt = *stmtp;
+		  update_stmt (stmt);
+		}
+
 	      /* Statements produced by inlining can be unfolded, especially
 		 when we constant propagated some operands.  We can't fold
 		 them right now for two reasons:
@@ -2539,6 +2585,7 @@ expand_call_inline (basic_block bb, tree
   id->src_fn = fn;
   id->src_node = cg_edge->callee;
   id->src_cfun = DECL_STRUCT_FUNCTION (fn);
+  id->call_expr = t;
 
   initialize_inlined_parameters (id, t, fn, bb);
 
--- gcc/builtins.c.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/builtins.c	2007-08-30 18:46:59.000000000 +0200
@@ -6270,6 +6270,12 @@ expand_builtin (tree exp, rtx target, rt
     case BUILT_IN_ARGS_INFO:
       return expand_builtin_args_info (exp);
 
+    case BUILT_IN_VA_ARG_PACK:
+      /* All valid uses of __builtin_va_arg_pack () are removed during
+	 inlining.  */
+      error ("invalid use of %<__builtin_va_arg_pack ()%>");
+      return const0_rtx;
+
       /* Return the address of the first anonymous stack arg.  */
     case BUILT_IN_NEXT_ARG:
       if (fold_builtin_next_arg (exp, false))
@@ -10413,14 +10419,26 @@ fold_call_expr (tree exp, bool ignore)
   tree fndecl = get_callee_fndecl (exp);
   if (fndecl
       && TREE_CODE (fndecl) == FUNCTION_DECL
-      && DECL_BUILT_IN (fndecl))
+      && DECL_BUILT_IN (fndecl)
+      && !CALL_EXPR_VA_ARG_PACK (exp))
     {
+      int nargs = call_expr_nargs (exp);
+
+      if (nargs && TREE_CODE (CALL_EXPR_ARG (exp, nargs - 1)) == CALL_EXPR)
+	{
+	  tree fndecl2 = get_callee_fndecl (CALL_EXPR_ARG (exp, nargs - 1));
+	  if (fndecl2
+	      && TREE_CODE (fndecl2) == FUNCTION_DECL
+	      && DECL_BUILT_IN (fndecl2)
+	      && DECL_FUNCTION_CODE (fndecl2) == BUILT_IN_VA_ARG_PACK)
+	    return NULL_TREE;
+	}
+
       /* FIXME: Don't use a list in this interface.  */
       if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD)
 	  return targetm.fold_builtin (fndecl, CALL_EXPR_ARGS (exp), ignore);
       else
 	{
-	  int nargs = call_expr_nargs (exp);
 	  if (nargs <= MAX_ARGS_TO_FOLD_BUILTIN)
 	    {
 	      tree *args = CALL_EXPR_ARGP (exp);
@@ -10506,6 +10524,15 @@ fold_builtin_call_array (tree type,
     if (TREE_CODE (fndecl) == FUNCTION_DECL
         && DECL_BUILT_IN (fndecl))
       {
+	if (n && TREE_CODE (argarray[n - 1]) == CALL_EXPR)
+	  {
+	    tree fndecl2 = get_callee_fndecl (argarray[n - 1]);
+	    if (fndecl2
+		&& TREE_CODE (fndecl2) == FUNCTION_DECL
+		&& DECL_BUILT_IN (fndecl2)
+		&& DECL_FUNCTION_CODE (fndecl2) == BUILT_IN_VA_ARG_PACK)
+	      return build_call_array (type, fn, n, argarray);
+	  }
         if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD)
           {
             tree arglist = NULL_TREE;
--- gcc/gimplify.c.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/gimplify.c	2007-08-30 20:50:19.000000000 +0200
@@ -2174,8 +2174,36 @@ gimplify_call_expr (tree *expr_p, tree *
 	    }
 	}
     }
-  else if (nargs != 0)
-    CALL_CANNOT_INLINE_P (*expr_p) = 1;
+  else
+    {
+      if (nargs != 0)
+	CALL_CANNOT_INLINE_P (*expr_p) = 1;
+      i = 0;
+      p = NULL_TREE;
+    }
+
+  /* If the last argument is __builtin_va_arg_pack () and it is not
+     passed as a named argument, decrease the number of CALL_EXPR
+     arguments and set instead the CALL_EXPR_VA_ARG_PACK flag.  */
+  if (!p
+      && i < nargs
+      && TREE_CODE (CALL_EXPR_ARG (*expr_p, nargs - 1)) == CALL_EXPR)
+    {
+      tree last_arg = CALL_EXPR_ARG (*expr_p, nargs - 1);
+      tree last_arg_fndecl = get_callee_fndecl (last_arg);
+
+      if (last_arg_fndecl
+	  && TREE_CODE (last_arg_fndecl) == FUNCTION_DECL
+	  && DECL_BUILT_IN (last_arg_fndecl)
+	  && DECL_FUNCTION_CODE (last_arg_fndecl) == BUILT_IN_VA_ARG_PACK)
+	{
+	  CALL_EXPR_ARG (*expr_p, nargs - 1) = NULL_TREE;
+	  VL_EXP_CHECK (*expr_p)->exp.operands[0]
+	    = build_int_cst (sizetype, VL_EXP_OPERAND_LENGTH (*expr_p) - 1);
+	  CALL_EXPR_VA_ARG_PACK (*expr_p) = 1;
+	  --nargs;
+	}
+    }
 
   /* Finally, gimplify the function arguments.  */
   for (i = (PUSH_ARGS_REVERSED ? nargs - 1 : 0);
--- gcc/expr.c.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/expr.c	2007-08-30 18:46:59.000000000 +0200
@@ -7935,6 +7935,10 @@ expand_expr_real_1 (tree exp, rtx target
       return expand_expr (OBJ_TYPE_REF_EXPR (exp), target, tmode, modifier);
 
     case CALL_EXPR:
+      /* All valid uses of __builtin_va_arg_pack () are removed during
+	 inlining.  */
+      if (CALL_EXPR_VA_ARG_PACK (exp))
+	error ("invalid use of %<__builtin_va_arg_pack ()%>");
       /* Check for a built-in function.  */
       if (TREE_CODE (CALL_EXPR_FN (exp)) == ADDR_EXPR
 	  && (TREE_CODE (TREE_OPERAND (CALL_EXPR_FN (exp), 0))
--- gcc/doc/extend.texi.jj	2007-08-29 13:47:47.000000000 +0200
+++ gcc/doc/extend.texi	2007-08-30 20:40:14.000000000 +0200
@@ -556,6 +556,31 @@ the containing function.  You should spe
 returned by @code{__builtin_apply}.
 @end deftypefn
 
+@deftypefn {Built-in Function} __builtin_va_arg_pack ()
+This built-in function represents all anonymous arguments of an inline
+function.  It can be used only in inline functions which will be always
+inlined, never compiled as a separate function, such as those using
+@code{__attribute__ ((__always_inline__))} or
+@code{__attribute__ ((__gnu_inline__))} extern inline functions.
+It must be only passed as last argument to some other function
+with variable arguments.  This is useful for writing small wrapper
+inlines for variable argument functions, when using preprocessor
+macros is undesirable.  For example:
+@smallexample
+extern inline __attribute__ ((__always_inline__)) int
+myprintf (FILE *f, const char *format, ...)
+@{
+  int r = fprintf (f, "myprintf: ");
+  if (r < 0)
+    return r;
+  int s = fprintf (f, format, __builtin_va_arg_pack ());
+  if (s < 0)
+    return s;
+  return r + s;
+@}
+@end smallexample
+@end deftypefn
+
 @node Typeof
 @section Referring to a Type with @code{typeof}
 @findex typeof
--- gcc/tree-pretty-print.c.jj	2007-08-30 18:38:44.000000000 +0200
+++ gcc/tree-pretty-print.c	2007-08-30 18:46:59.000000000 +0200
@@ -1218,6 +1218,15 @@ dump_generic_node (pretty_printer *buffe
 	      }
 	  }
       }
+      if (CALL_EXPR_VA_ARG_PACK (node))
+	{
+	  if (call_expr_nargs (node) > 0)
+	    {
+	      pp_character (buffer, ',');
+	      pp_space (buffer);
+	    }
+	  pp_string (buffer, "__builtin_va_arg_pack ()");
+	}
       pp_character (buffer, ')');
 
       op1 = CALL_EXPR_STATIC_CHAIN (node);
--- gcc/testsuite/gcc.c-torture/execute/va-arg-pack-1.c.jj	2007-08-30 21:35:14.000000000 +0200
+++ gcc/testsuite/gcc.c-torture/execute/va-arg-pack-1.c	2007-08-30 21:34:25.000000000 +0200
@@ -0,0 +1,143 @@
+/* __builtin_va_arg_pack () builtin tests.  */
+
+#include <stdarg.h>
+
+extern void abort (void);
+
+int v1 = 8;
+long int v2 = 3;
+void *v3 = (void *) &v2;
+struct A { char c[16]; } v4 = { "foo" };
+long double v5 = 40;
+char seen[20];
+int cnt;
+
+__attribute__ ((noinline)) int
+foo1 (int x, int y, ...)
+{
+  int i;
+  long int l;
+  void *v;
+  struct A a;
+  long double ld;
+  va_list ap;
+
+  va_start (ap, y);
+  if (x < 0 || x >= 20 || seen[x])
+    abort ();
+  seen[x] = ++cnt;
+  if (y != 6)
+    abort ();
+  i = va_arg (ap, int);
+  if (i != 5)
+    abort ();
+  switch (x)
+    {
+    case 0:
+      i = va_arg (ap, int);
+      if (i != 9 || v1 != 9)
+	abort ();
+      a = va_arg (ap, struct A);
+      if (__builtin_memcmp (a.c, v4.c, sizeof (a.c)) != 0)
+	abort ();
+      v = (void *) va_arg (ap, struct A *);
+      if (v != (void *) &v4)
+	abort ();
+      l = va_arg (ap, long int);
+      if (l != 3 || v2 != 4)
+	abort ();
+      break;
+    case 1:
+      ld = va_arg (ap, long double);
+      if (ld != 41 || v5 != ld)
+	abort ();
+      i = va_arg (ap, int);
+      if (i != 8)
+	abort ();
+      v = va_arg (ap, void *);
+      if (v != &v2)
+	abort ();
+      break;
+    case 2:
+      break;
+    default:
+      abort ();
+    }
+  va_end (ap);
+  return x;
+}
+
+__attribute__ ((noinline)) int
+foo2 (int x, int y, ...)
+{
+  long long int ll;
+  void *v;
+  struct A a, b;
+  long double ld;
+  va_list ap;
+
+  va_start (ap, y);
+  if (x < 0 || x >= 20 || seen[x])
+    abort ();
+  seen[x] = ++cnt | 64;
+  if (y != 10)
+    abort ();
+  switch (x)
+    {
+    case 11:
+      break;
+    case 12:
+      ld = va_arg (ap, long double);
+      if (ld != 41 || v5 != 40)
+	abort ();
+      a = va_arg (ap, struct A);
+      if (__builtin_memcmp (a.c, v4.c, sizeof (a.c)) != 0)
+	abort ();
+      b = va_arg (ap, struct A);
+      if (__builtin_memcmp (b.c, v4.c, sizeof (b.c)) != 0)
+	abort ();
+      v = va_arg (ap, void *);
+      if (v != &v2)
+	abort ();
+      ll = va_arg (ap, long long int);
+      if (ll != 16LL)
+	abort ();
+      break;
+    case 2:
+      break;
+    default:
+      abort ();
+    }
+  va_end (ap);
+  return x + 8;
+}
+
+__attribute__ ((noinline)) int
+foo3 (void)
+{
+  return 6;
+}
+
+extern inline __attribute__ ((always_inline, gnu_inline)) int
+bar (int x, ...)
+{
+  if (x < 10)
+    return foo1 (x, foo3 (), 5, __builtin_va_arg_pack ());
+  return foo2 (x, foo3 () + 4, __builtin_va_arg_pack ());
+}
+
+int
+main (void)
+{
+  if (bar (0, ++v1, v4, &v4, v2++) != 0)
+    abort ();
+  if (bar (1, ++v5, 8, v3) != 1)
+    abort ();
+  if (bar (2) != 2)
+    abort ();
+  if (bar (v1 + 2) != 19)
+    abort ();
+  if (bar (v1 + 3, v5--, v4, v4, v3, 16LL) != 20)
+    abort ();
+  return 0;
+}
--- gcc/testsuite/gcc.dg/va-arg-pack-1.c.jj	2007-08-30 21:42:32.000000000 +0200
+++ gcc/testsuite/gcc.dg/va-arg-pack-1.c	2007-08-30 21:45:31.000000000 +0200
@@ -0,0 +1,52 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+int bar (int, const char *, int, ...);
+int baz (int, const char *, long int);
+
+int
+f1 (int x, ...)
+{
+  return bar (5, "", 6, __builtin_va_arg_pack ());	/* { dg-error "invalid use of" } */
+}
+
+extern inline __attribute__((always_inline)) int
+f2 (int y, ...)
+{
+  return bar (y, "", __builtin_va_arg_pack ());		/* { dg-error "invalid use of" } */
+}
+
+extern inline __attribute__((always_inline)) int
+f3 (int y, ...)
+{
+  return bar (y, "", 5, __builtin_va_arg_pack ());
+}
+
+extern inline __attribute__((always_inline)) int
+f4 (int y, ...)
+{
+  return bar (y, "", 4, __builtin_va_arg_pack (), 6);	/* { dg-error "invalid use of" } */
+}
+
+extern inline __attribute__((always_inline)) int
+f5 (int y, ...)
+{
+  return baz (y, "", __builtin_va_arg_pack ());		/* { dg-error "invalid use of" } */
+}
+
+extern inline __attribute__((always_inline)) int
+f6 (int y, ...)
+{
+  return __builtin_va_arg_pack ();			/* { dg-error "invalid use of" } */
+}
+
+int
+test (void)
+{
+  int a = f2 (5, "a", 6);
+  a += f3 (6, "ab", 17LL);
+  a += f4 (7, 1, 2, 3);
+  a += f5 (8, 7L);
+  a += f6 (9);
+  return a;
+}

	Jakub


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]