Accessing arguments in a caller

I need some advice on “forwarding” arguments to a callee. Suppose I have a function F that is called at the beginning of all other functions in the module. From F I need to access (read) the arguments passed to its immediate caller. Right now I do something like boxing all arguments in the caller inside a struct and passing a pointer to the struct to F, alongside an identifier telling which caller F is being called from. F has then a giant switch that branches to the appropriate unboxing code. This is obviously suboptimal because I need to allocate the struct on the stack, copy the arguments inside of it and then passing an additional pointer to F.
I was wondering if there’s a better way to do this, e.g. a way to access the stack frame of the caller (knowing, thanks to the identifier, which caller F was called from) or, more in general, arbitrary values defined in the caller. Any suggestions?

Nella citazione giovedì 18 agosto 2011 09:11:36, Carlo Alberto Ferraris ha scritto:

I need some advice on "forwarding" arguments to a callee. Suppose I have a function F that is called at the beginning of all other functions in the module. From F I need to access (read) the arguments passed to its immediate caller. Right now I do something like boxing all arguments in the caller inside a struct and passing a pointer to the struct to F, alongside an identifier telling which caller F is being called from. F has then a giant switch that branches to the appropriate unboxing code. This is obviously suboptimal because I need to allocate the struct on the stack, copy the arguments inside of it and then passing an additional pointer to F.
I was wondering if there's a better way to do this, e.g. a way to access the stack frame of the caller (knowing, thanks to the identifier, which caller F was called from) or, more in general, arbitrary values d

efined

in the caller. Any suggestions?

Are there really no better ways of doing this?

What I can think of:
1. Anonymous struct, to avoid a copy.
2. Use stdarg.h...dangerous but accomplishes what you're looking for.
3. Split up F: giant switch statements often indicate that your function is doing several different things. (IMHO...)
4. Don't worry about it, it's probably not a bottleneck. (Or rather, profile first...)

But no, there's no standard way to do this. Even C++11's variadic templates don't avoid the copy, and initializer lists are pretty much equivalent to an anonymous struct / const struct reference.

(Why not? Because calling conventions differ from platform to platform; often the first few arguments of a function are passed in registers, not on the stack.)

Jordy

What I can think of:
1. Anonymous struct, to avoid a copy.
2. Use stdarg.h...dangerous but accomplishes what you're looking for.

probably it wasn’t clear from my first post: I’m talking about LLVM-IR, not C or C++.

3. Split up F: giant switch statements often indicate that your function is doing several different things. (IMHO...)

The whole point of the work I’m doing is having a single function; there’s no way around that.

4. Don't worry about it, it's probably not a bottleneck. (Or rather, profile first...)

It might not be the most important thing right now, but it will quickly become important the further the project moves. By disassembling the genereated executable there’s a lot of things that pop out as inefficient.

But no, there's no standard way to do this. Even C++11's variadic templates don't avoid the copy, and initializer lists are pretty much equivalent to an anonymous struct / const struct reference.

(Why not? Because calling conventions differ from platform to platform; often the first few arguments of a function are passed in registers, not on the stack.)

That’s why I was hoping that LLVM-IR had a better way of doing such things (I mean, telling the backend that I need to access the stack frame of the caller).

BTW, I posted the same question on SO, along with some (pseudo-) code to explain what I’m doing right now:

Hi Carlo, rather than declaring individual stack variables
   int x;
   int y;
   int z;
and so on, which requires you to pass each one, or a pointer to each one,
to your function, declare one stack variable of struct type that holds
them all:
   struct StackObjects {
     int x;
     int y;
     int z;
     ...
   };
...
   struct StackObjects stack;
then pass the address of stack to your function, enabling it to access all
of the objects on the callers stack. This way you only need to copy a single
pointer. This is what GCC's nested function implementation does for example to
enable child functions to access variables on the parent's stack.

Ciao, Duncan.

Nella citazione lunedì 22 agosto 2011 11:53:29, Duncan Sands ha scritto:

Hi Carlo, rather than declaring individual stack variables
    int x;
    int y;
    int z;
and so on, which requires you to pass each one, or a pointer to each one,
to your function, declare one stack variable of struct type that holds
them all:
    struct StackObjects {
      int x;
      int y;
      int z;
      ...
    };
...
    struct StackObjects stack;
then pass the address of stack to your function, enabling it to access all
of the objects on the callers stack. This way you only need to copy a single
pointer. This is what GCC's nested function implementation does for example to
enable child functions to access variables on the parent's stack.

Hi Duncan,
this is what I'm already doing (see http://stackoverflow.com/questions/7144733/argument-forwarding-in-llvm, the code I posted is in pseudo-C but I'm actually working on LLVM-IR in my pass) but this in
curs the boxing-unboxing penalty and I was wondering if there is a way to avoid it.

Hi Carlo,

Nella citazione lunedì 22 agosto 2011 11:53:29, Duncan Sands ha scritto:

Hi Carlo, rather than declaring individual stack variables
int x;
int y;
int z;
and so on, which requires you to pass each one, or a pointer to each one,
to your function, declare one stack variable of struct type that holds
them all:
struct StackObjects {
int x;
int y;
int z;
...
};
...
struct StackObjects stack;
then pass the address of stack to your function, enabling it to access all
of the objects on the callers stack. This way you only need to copy a single
pointer. This is what GCC's nested function implementation does for example to
enable child functions to access variables on the parent's stack.

Hi Duncan,
this is what I'm already doing (see
http://stackoverflow.com/questions/7144733/argument-forwarding-in-llvm, the code
I posted is in pseudo-C but I'm actually working on LLVM-IR in my pass) but this in
curs the boxing-unboxing penalty and I was wondering if there is a way to avoid it.

I didn't read your question carefully enough: I thought you were talking about
accessing stack variables declared by the parent (in which case there is no cost
to using the above technique), but in fact you were talking about accessing the
parent's arguments. Probably the best you can do is store a pointer to each
argument in a stack struct object (like above), then pass a pointer to that
object. The setup cost is then one pointer store per argument, plus one pointer
copy (passing the stack object to the callee). The cost of accessing is one
pointer dereference per argument. I'm pretty sure this is what GCC does when
a nested function wants to access one of its parent's arguments.

Ciao, Duncan.

I see. Just wondering: what if I were to construct F as a function with a number of parameters high enough to be able to accept any combination of parameters of the other functions? Something like:

void A(int, float, float)
void B(float, struct S)

→ void fastcc F(int, float, float, struct S)

and then calling F with undefs for the unused parameters:

void A(int a, float b, float c) {
F(a, b, c, undef);
}

void B(float a, struct S b) {
F(undef, a, undef, b);
}

are the backends able to efficiently handle functions with many arguments (the majority of which would be undefs)? could this work?