jonathan@comp.vuw.ac.nz (07/13/89)
GCC version: GNU CC 1.35 Machine:: Pyramid 90x family. Symptom:: Functions using stdargs mysteriously lose their first unnamed argument. Repeat-by:: Compile and run the following program with GCC on a Pyramid. Notice the lack of a "1" in the output. #include <stdio.h> #include "stdarg.h" void test_stdarg (FILE * stream, ...); int main (argc, argv) int argc; char *argv []; { test_stdarg (stderr, 1, 2, 3, 4, 5, 6); return (0); } void test_stdarg ( FILE *stream, ...) { extern int _doprnt(); va_list ap; int i, arg; va_start(ap, stream); for (i =0; i< 6;i++) { arg = va_arg(ap, int); fprintf(stream, "%d ", arg); } fprintf(stream, "\n"); va_end(ap); } Output:: 2 3 4 5 6 0 Assembler Output:: Ommited. Analysis:: This problem is not caused by stdarg.h looking one arg further ahead than it should. I tried changing stdarg.h to make va_arg() return the argument *before* the correct one. With that change, the example above produce "8584 2 3 4 5 6". 8584 happens to be the value of stderr, the last named arg. (This gets long-winded and technical, starting now.) Running with the debugger on this and other examples shows that that expand_call() always passes the last named arg to FUNCTION_ARG with NAMED non-zero. The Pyramid FUNCTION_ARG obediently passes this arg in a register (if one is available). (It may be that the Pyramid md is not doing something right here; but it is doing exactly what the Internals manual and RMS say it should.) To say it another way, in expr.c, /* Decide where to pass this arg. */ /* args[i].reg is nonzero if all or part is passed in registers. args[i].partial is nonzero if part but not all is passed in registers, and the exact value says how many words are passed in registers. */ if (TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST && args_size.var == 0 /* error_mark_node here is a flag for the fake argument for a structure value address. */ && TREE_PURPOSE (p) != error_mark_node) { args[i].reg = FUNCTION_ARG (args_so_far, TYPE_MODE (type), type, i < n_named_args); /* If this argument needs more than the usual parm alignment, do extrinsic padding to reach that alignment. */ the expression "i < n_named_args" is true for the last named arg, so it gets passed in a register. It is false for all the unnamed args, so the md passes them all on the stack, as RMS says it should. When compiling the callee, stmt.c finds that this same last named argument is arriving in a register: if (TREE_CODE (TYPE_SIZE (TREE_TYPE (parm))) == INTEGER_CST || stack_offset.var != 0) { #ifdef FUNCTION_INCOMING_ARG entry_parm = FUNCTION_INCOMING_ARG (args_so_far, passed_mode, DECL_ARG_TYPE (parm), 1); #else entry_parm = FUNCTION_ARG (args_so_far, passed_mode, DECL_ARG_TYPE (parm), 1); #endif } /* If this parm was passed part in regs and part in memory, pretend it arrived entirely in memory by pushing the register-part onto the stack. In the special case of a DImode or DFmode that is split, we could put it together in a pseudoreg directly, but for now that's not worth bothering with. */ --> /* If this is the last named arg and anonymous args follow, --> likewise pretend this arg arrived on the stack so varargs can find the anonymous args following it. */ { int nregs = 0; int i; ... It subsequently builds RTXes to store the last named arg back into the first argument slot on the stack. This is exactly where the caller put the first unnamed arg, which gets clobbered. Fix:: Not obvious to me. I'm not even sure where the bug is. I suspect no-one has found this bug before because all other machines that pass args in registers have well-defined shadow locations on the stack, and the current code simply stores the last named arg into its location. This is not the case for Pyramids; args passed in regs have no natural home on the stack. It's easy to fix the caller side by making expand_expr pass the last named arg on the stack: *** /usr/src/gnu/gcc/expr.c Wed May 24 10:46:14 1989 --- ./expr.c Thu Jul 13 19:56:26 1989 *************** *** 3887,3893 **** --- 3887,3897 ---- This may actually be 1 too large, but that happens only in the case when all args are named, so no trouble results. */ if (TYPE_ARG_TYPES (funtype) != 0) + #if 0 n_named_args = list_length (TYPE_ARG_TYPES (funtype)); + #else + n_named_args = list_length (TYPE_ARG_TYPES (funtype)) -1; + #endif else /* If we know nothing, treat all args as named. */ n_named_args = num_actuals; But this doesn't help the callee. FUNCTION_INCOMING_ARG is only ever called in one place, and NAMED is *always* one, so assign_parms will always believe that the last named arg came in a register (should one be available). Perhaps this should be changed to zero for last named args? I'm not sure what effect such changes will have on other machines that pass args in registers. Mips machines allocate a stack slot even for args that are passed in registers, so the current code shoul work on a Mips. How about Sparc? Is the approach I suggest in any case the right sort of things to do? If not, what is?