StackThreads/MP: version 0.77 User's Guide
Here we describe what these patches do in detail. The purpose is to give information to an expert. The following description makes sense only when you understand what is the problem with the original gcc. See Floating SP Problem and Structure Passing on SPARC for the description of the problems with the original gcc.
calls.c
in function expand_call
:
There is a loop that determines, for each argument to a procedure call
expression, whether the evaluated value of the argument should be placed
on a temporary location or can be directly placed on the stack. The loop
in gcc 2.7.2.3 looks like this (including preceeding comments).
@ 1: /* If we preallocated the stack space, and some ... @ 2: @ 3: ... @ 4: @ 5: ... into the stack. */ @ 6: @ 7: for (i = 0; i < num_actuals; i++) @ 8: if (is_const @ 9: || ((args_size.var != 0 || args_size.constant != 0) 10: && calls_function (args[i].tree_value, 1)) 11: || (must_preallocate && (args_size.var != 0 || args_size.constant != 0) 12: && calls_function (args[i].tree_value, 0))) 13: { 14: /* If this is an addressable type, we cannot pre-evaluate it. */ 15: if (TREE_ADDRESSABLE (TREE_TYPE (args[i].tree_value))) 16: abort (); 17: ...
This loop judges whether each argument in turn includes a nested
procedure call or a call to alloca
function.
calls_function(a, 1)
returns 1 if expression a includes a
call to alloca
, and calls_function(a, 0)
returns 1 if
expression a includes any procedure call. The essential modification
is to change the second parameter to calls_function
at line 10 to
zero, which was originally one. This essentially treats any function
call as if it were a call to alloca
.
The patch passes zero to calls_function
at line 10 only when a
flag (flag_calls_clobber_sp
) is set. It also guards the
modification by a #if
directive in the way described earlier in
this chapter. The resulting code looks like this:
@ /* If we preallocated the stack space, and some arguments must be passed @ on the stack, then we must precompute any parameter which contains a @ function call which will store arguments on the stack. @ Otherwise, evaluating the parameter may clobber previous parameters @ which have already been stored into the stack. */ @ @ for (i = 0; i < num_actuals; i++) @ if (is_const @ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ || ((args_size.var != 0 || args_size.constant != 0) @ && calls_function (args[i].tree_value, @ (flag_calls_clobber_sp ? 0 : 1))) @ #else /* !defined(STHREADS_SAFE) || STHREADS_SAFE */ @ || ((args_size.var != 0 || args_size.constant != 0) @ && calls_function (args[i].tree_value, 1)) @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */ @ || (must_preallocate && (args_size.var != 0 || args_size.constant != 0) @ && calls_function (args[i].tree_value, 0))) @ { @ /* If this is an addressable type, we cannot pre-evaluate it. */ @ if (TREE_ADDRESSABLE (TREE_TYPE (args[i].tree_value))) @ abort ();
What this patch essentially does is to tell gcc that any function call
may alter the value of stack pointer in an unexpected way, just as
alloca
does. On Pentium, where stack pointer is allowed to move
to push and pop arguments to procedure calls, the compiler can push an
argument on stack top as soon as it is evaluated, no matter whether
other arguments have a nested procedure call. A nested procedure call
can push its arguments on top of the arguments under construction for
the outer procedure call. When the nested procedure call returns, its
arguments can be freed and the rest of the arguments to the outer
procedure call can then be pushed on the stack. There is one exception
to this rule; when a nested procedure call is a call to alloca
,
the compiler cannot infer the value of stack pointer after the call,
hence cannot place arguments evaluated before alloca
on top of
the stack at that point. It instead must perform calls to alloca
first and evaluate the rest of the arguments. The results from
alloca
s must be saved in temporary slots, which are addressed via
frame pointer and then moved to stack top just before calling the outer
function.
By treating any function call as if it were a call to alloca
, we
effectively force gcc to pre-evaluate all nested procedure calls.
flags.h
: Just add a declaration of
flag flag_calls_clobber_sp
, which is set when a command line
option -fcalls-clobber-sp
is given. Add the following piece of
code at the end of the series of declarations of flags.
@ /* Non-zero any function call is supposed to clobber SP */ @ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ extern int flag_calls_clobber_sp; @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
toplev.c
: Just add a definition of flag
flag_calls_clobber_sp
and update the table of flags
(f_options
). Add the following piece of code at the end of the
series of definitions of flags.
@ /* Non-zero any function call is supposed to clobber SP */ @ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ int flag_calls_clobber_sp = 0; @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
Also add the following at the end of the table f_options
:
@ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ , {"calls-clobber-sp", &flag_calls_clobber_sp, 1} @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
function.c
in function assign_parms
:
There is a code fragment that copies incoming parameters that are passed
by an invisible reference, if necessary. Search
FUNCTION_ARG_CALLEE_COPIES
. You will find a fragment that looks
like:
@ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ #ifdef FUNCTION_ARG_MAY_BE_DESTROYED @ /* If we are passed an arg by reference and it may be destroyed @ for any unknown reason (even if the callee does not modify @ it by itself). @ PASSED_TYPE and PASSED mode now refer to the pointer, not the @ original argument, so we must recreate them in the call to @ FUNCTION_ARG_CALLEE_COPIES. @ */ @ @ else if (passed_pointer @ && FUNCTION_ARG_MAY_BE_DESTROYED (args_so_far, @ TYPE_MODE (DECL_ARG_TYPE (parm)), @ DECL_ARG_TYPE (parm), @ ! last_named) @ && ! TREE_ADDRESSABLE (DECL_ARG_TYPE (parm))) @ { @ rtx copy; @ tree type = DECL_ARG_TYPE (parm); @ @ /* This sequence may involve a library call perhaps clobbering @ registers that haven't been copied to pseudos yet. */ @ ...
This is an else if
branch of a larger if
statement. Make a
copy of the original else if
branch, guard it by an #if
directive, and insert it before the original one. Then, replace
FUNCTION_ARG_CALLEE_COPIES
with
FUNCTION_ARG_MAY_BE_DESTROYED
in the new branch. That is, the
entire if
statement now looks like:
@ /* If we were passed a pointer but the actual value @ can safely live in a register, put it in one. */ @ if (passed_pointer && TYPE_MODE (TREE_TYPE (parm)) != BLKmode @ ... @ ) @ { @ ... @ } @ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ #ifdef FUNCTION_ARG_MAY_BE_DESTROYED @ /* ... */ @ else if (passed_pointer @ && FUNCTION_ARG_MAY_BE_DESTROYED (args_so_far, @ ...) @ && ! TREE_ADDRESSABLE (DECL_ARG_TYPE (parm))) @ { @ rtx copy; @ tree type = DECL_ARG_TYPE (parm); @ ... @ } @ #endif /* FUNCTION_ARG_MAY_BE_DESTROYED */ @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */ @ @ #ifdef FUNCTION_ARG_CALLEE_COPIES @ /* ... */ @ else if (passed_pointer @ && FUNCTION_ARG_CALLEE_COPIES (args_so_far, @ ...) @ && ! TREE_ADDRESSABLE (DECL_ARG_TYPE (parm))) @ { @ rtx copy; @ tree type = DECL_ARG_TYPE (parm); @ ... @ } @ #endif /* FUNCTION_ARG_CALLEE_COPIES */
The inserted branch handles the case in which an incoming argument passed by reference may be destroyed for any unknown reason. The callee copies such arguments to its own place. GCC happens to do the same thing for handling cases in which an argument is passed by reference and it is the callee's responsibility to copy it before modifying the location.
Although duplicating code in this way makes maintenance troublesome, and
there is a way to handle both cases in a single else if
branch,
we currently treat them in two separate else if
branches. Reasons
are as follows:
FUNCTION_ARG_CALLEE_COPIES(...)
is non-zero) may be improved
in future; if gcc recognizes cases where the argument is not modified by
the callee, the callee does not have to copy it. On the other hand, the
inserted branch must copy it even when the callee itself does not modify
it.
else if
branch
by #ifdef FUNCTION_ARG_CALLEE_COPIES
. This makes it difficult to
add anything that must be effective even if
FUNCTION_ARG_CALLEE_COPIES
is not defined, without modifying the
line #ifdef FUNCTION_ARG_CALLEE_COPIES
.
config/sparc/sparc.h
:
MASK_CALLEE_COPIES_STRUCT_ARGS
and
TARGET_CALLEE_COPIES_STRUCT_ARGS
.
To determine the correct value for
MASK_CALLEE_COPIES_STRUCT_ARGS
, look through sparc.h
and
search all definitions that look like #define MASK_xxx
. Any mask
is a power of two and is a mask to check if a particular SPARC-specific
flag is defined. Add a new mask to this series of masks. For example, if
you find that the last mask defined in the file is 0x400000
, you
can use 0x800000
for MASK_CALLEE_COPIES_STRUCT_ARGS
,
leading to the following definition:
#define MASK_CALLEE_COPIES_STRUCT_ARGS 0x800000
Always define TARGET_CALLEE_COPIES_STRUCT_ARGS
like this:
#define TARGET_CALLEE_COPIES_STRUCT_ARGS \ @ (target_flags & MASK_CALLEE_COPIES_STRUCT_ARGS)
To summarize, you add the following lines:
@ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ #define MASK_CALLEE_COPIES_STRUCT_ARGS 0x800000 @ #define TARGET_CALLEE_COPIES_STRUCT_ARGS \ @ (target_flags & MASK_CALLEE_COPIES_STRUCT_ARGS) @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
-mcallee-copies-struct-args
to TARGET_SWITCHES
macro. Search #define TARGET_SWITCHES
and add a line {"callee-copies-struct-args", MASK_CALLEE_COPIES_STRUCT_ARGS} \
to an appropriate place. Do not
forget the trailing backslash, which is necessary because it is in a
macro definition. To guard the modification by an #if
directive,
we have to copy the entire definition, leading to the following code:
@ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ #define TARGET_SWITCHES \ @ { {"fpu", MASK_FPU | MASK_FPU_SET}, \ @ {"no-fpu", -MASK_FPU}, \ @ ... @ {"64", MASK_64BIT}, \ @ {"stack-bias", MASK_STACK_BIAS}, \ @ {"no-stack-bias", -MASK_STACK_BIAS}, \ @ {"callee-copies-struct-args", MASK_CALLEE_COPIES_STRUCT_ARGS}, \ @ SUBTARGET_SWITCHES \ @ { "", TARGET_DEFAULT}} @ #else /* !defined(STHREADS_SAFE) || STHREADS_SAFE */ @ #define TARGET_SWITCHES \ @ {{"fpu", MASK_FPU | MASK_FPU_SET}, \ @ {"no-fpu", -MASK_FPU}, \ @ ... @ {"64", MASK_64BIT}, \ @ {"stack-bias", MASK_STACK_BIAS}, \ @ {"no-stack-bias", -MASK_STACK_BIAS}, \ @ SUBTARGET_SWITCHES \ @ { "", TARGET_DEFAULT}} @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
FUNCTION_ARG_MAY_BE_DESTROYED(CUM, MODE, TYPE, NAMED)
as follows:
@ /* non-zero if an argument passed by reference may be destroyed @ for any unknown reason, even if the callee does not write @ to it by itself. */ @ #if !defined(STHREADS_SAFE) || STHREADS_SAFE @ #define FUNCTION_ARG_MAY_BE_DESTROYED(CUM, MODE, TYPE, NAMED) \ @ TARGET_CALLEE_COPIES_STRUCT_ARGS @ #endif /* !defined(STHREADS_SAFE) || STHREADS_SAFE */
This macro takes parameters just for possible future extensions and for
consistency with existing macro FUNCTION_ARG_CALLEE_COPIES
.
These patches essentially force a callee to copy incoming structure
parameters to its private place. In the original SPARC v8 calling
convention, when a procedure passes a structure to another procedure by
value, it copies the structure to a place in the caller's frame and
passes the pointer to it to the callee. The callee can assume that the
structure persists throughout the lifetime of the procedure
call. Setting FUNCTION_ARG_MAY_BE_DESTROYED(CUM, MODE, TYPE, NAMED)
to non-zero tells gcc that this no longer holds, therefore the
callee must copy its incoming structure parameters to its own place, no
matter whether it modifies these arguments or not.