Given the unacceptability of the original builtins patch: http://gcc.gnu.org/ml/gcc-patches/2012-01/msg01564.html
I've decided to go for this MIPS-specific version. I've also made the test specific to MIPS and XFAILed it for EABI32 (the failures there don't seem to be a regression). We could potentially make both mips_std_gimplify_va_arg_expr and the testcase available to all targets later. This shows that mips_gimplify_va_arg_expr ought to be testing EABI_FLOAT_VARARGS_P at the very beginning. Stuff like: indirect_p = pass_by_reference (NULL, TYPE_MODE (type), type, 0); if (indirect_p) type = build_pointer_type (type); is at best redundant for !EABI_FLOAT_VARARGS_P, since the generic functions do the same thing. That's 4.8 material though. Tested on mips64-linux-gnu and various ELF targets. Applied. Richard gcc/ PR middle-end/24306 * config/mips/mips.c (mips_std_gimplify_va_arg_expr): New function. (mips_gimplify_va_arg_expr): Call it instead of std_gimplify_va_arg_expr. gcc/testsuite/ PR middle-end/24306 PR target/52154 * lib/target-supports.exp (check_effective_target_mips_eabi): New. * gcc.target/mips/va-arg-1.c: New test. Index: gcc/config/mips/mips.c =================================================================== --- gcc/config/mips/mips.c 2012-02-05 14:49:20.000000000 +0000 +++ gcc/config/mips/mips.c 2012-02-06 20:37:25.000000000 +0000 @@ -5576,6 +5576,95 @@ mips_va_start (tree valist, rtx nextarg) } } +/* Like std_gimplify_va_arg_expr, but apply alignment to zero-sized + types as well. */ + +static tree +mips_std_gimplify_va_arg_expr (tree valist, tree type, gimple_seq *pre_p, + gimple_seq *post_p) +{ + tree addr, t, type_size, rounded_size, valist_tmp; + unsigned HOST_WIDE_INT align, boundary; + bool indirect; + + indirect = pass_by_reference (NULL, TYPE_MODE (type), type, false); + if (indirect) + type = build_pointer_type (type); + + align = PARM_BOUNDARY / BITS_PER_UNIT; + boundary = targetm.calls.function_arg_boundary (TYPE_MODE (type), type); + + /* When we align parameter on stack for caller, if the parameter + alignment is beyond MAX_SUPPORTED_STACK_ALIGNMENT, it will be + aligned at MAX_SUPPORTED_STACK_ALIGNMENT. We will match callee + here with caller. */ + if (boundary > MAX_SUPPORTED_STACK_ALIGNMENT) + boundary = MAX_SUPPORTED_STACK_ALIGNMENT; + + boundary /= BITS_PER_UNIT; + + /* Hoist the valist value into a temporary for the moment. */ + valist_tmp = get_initialized_tmp_var (valist, pre_p, NULL); + + /* va_list pointer is aligned to PARM_BOUNDARY. If argument actually + requires greater alignment, we must perform dynamic alignment. */ + if (boundary > align) + { + t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp, + fold_build_pointer_plus_hwi (valist_tmp, boundary - 1)); + gimplify_and_add (t, pre_p); + + t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist_tmp, + fold_build2 (BIT_AND_EXPR, TREE_TYPE (valist), + valist_tmp, + build_int_cst (TREE_TYPE (valist), -boundary))); + gimplify_and_add (t, pre_p); + } + else + boundary = align; + + /* If the actual alignment is less than the alignment of the type, + adjust the type accordingly so that we don't assume strict alignment + when dereferencing the pointer. */ + boundary *= BITS_PER_UNIT; + if (boundary < TYPE_ALIGN (type)) + { + type = build_variant_type_copy (type); + TYPE_ALIGN (type) = boundary; + } + + /* Compute the rounded size of the type. */ + type_size = size_in_bytes (type); + rounded_size = round_up (type_size, align); + + /* Reduce rounded_size so it's sharable with the postqueue. */ + gimplify_expr (&rounded_size, pre_p, post_p, is_gimple_val, fb_rvalue); + + /* Get AP. */ + addr = valist_tmp; + if (PAD_VARARGS_DOWN && !integer_zerop (rounded_size)) + { + /* Small args are padded downward. */ + t = fold_build2_loc (input_location, GT_EXPR, sizetype, + rounded_size, size_int (align)); + t = fold_build3 (COND_EXPR, sizetype, t, size_zero_node, + size_binop (MINUS_EXPR, rounded_size, type_size)); + addr = fold_build_pointer_plus (addr, t); + } + + /* Compute new value for AP. */ + t = fold_build_pointer_plus (valist_tmp, rounded_size); + t = build2 (MODIFY_EXPR, TREE_TYPE (valist), valist, t); + gimplify_and_add (t, pre_p); + + addr = fold_convert (build_pointer_type (type), addr); + + if (indirect) + addr = build_va_arg_indirect_ref (addr); + + return build_va_arg_indirect_ref (addr); +} + /* Implement TARGET_GIMPLIFY_VA_ARG_EXPR. */ static tree @@ -5590,7 +5679,7 @@ mips_gimplify_va_arg_expr (tree valist, type = build_pointer_type (type); if (!EABI_FLOAT_VARARGS_P) - addr = std_gimplify_va_arg_expr (valist, type, pre_p, post_p); + addr = mips_std_gimplify_va_arg_expr (valist, type, pre_p, post_p); else { tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff; Index: gcc/testsuite/lib/target-supports.exp =================================================================== --- gcc/testsuite/lib/target-supports.exp 2012-02-06 20:37:48.000000000 +0000 +++ gcc/testsuite/lib/target-supports.exp 2012-02-06 20:38:33.000000000 +0000 @@ -924,6 +924,19 @@ proc check_effective_target_mips_rel { } }] } +# Return true if the target is a MIPS target that uses the EABI. + +proc check_effective_target_mips_eabi { } { + if { ![istarget mips*-*-*] } { + return 0 + } + return [check_no_compiler_messages mips_eabi object { + #ifndef __mips_eabi + #error FOO + #endif + }] +} + # Return 1 if the current multilib does not generate PIC by default. proc check_effective_target_nonpic { } { Index: gcc/testsuite/gcc.target/mips/va-arg-1.c =================================================================== --- /dev/null 2012-02-07 18:03:08.281654194 +0000 +++ gcc/testsuite/gcc.target/mips/va-arg-1.c 2012-02-07 18:50:19.000000000 +0000 @@ -0,0 +1,48 @@ +/* See PR 52154 for the xfail. */ +/* { dg-do run { xfail { mips_eabi && { hard_float && ilp32 } } } } */ + +#include <stdarg.h> + +extern void abort (void); + +struct __attribute__((aligned(16))) empty {}; + +static void __attribute__((noinline)) +check_args (int count, ...) +{ + va_list va; + int i; + + va_start (va, count); + for (i = 0; i < count; i++) + if (va_arg (va, int) != 1000 + i) + abort (); + + va_arg (va, struct empty); + if (va_arg (va, int) != 2000 + count) + abort (); + + va_end (va); +} + +int +main (void) +{ + struct empty e; + + check_args (1, 1000, e, 2001); + check_args (2, 1000, 1001, e, 2002); + check_args (3, 1000, 1001, 1002, e, 2003); + check_args (4, 1000, 1001, 1002, 1003, e, 2004); + check_args (5, 1000, 1001, 1002, 1003, 1004, e, 2005); + check_args (6, 1000, 1001, 1002, 1003, 1004, 1005, e, 2006); + check_args (7, 1000, 1001, 1002, 1003, 1004, 1005, 1006, e, 2007); + check_args (8, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, e, 2008); + check_args (9, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, + 1008, e, 2009); + check_args (10, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, + 1008, 1009, e, 2010); + check_args (11, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, + 1008, 1009, 1010, e, 2011); + return 0; +}