How to build your own stack frames?

bliss@worm.convex.com (Brian Bliss)
Thu, 24 Aug 1995 18:57:33 GMT

          From comp.compilers

Related articles
How to build your own stack frames? (interpreter questions) seguin@engin.umich.edu (1995-08-20)
How to build your own stack frames? bliss@worm.convex.com (1995-08-24)
| List of all articles for this month |

Newsgroups: comp.compilers
From: bliss@worm.convex.com (Brian Bliss)
Keywords: interpreter, C++
Organization: Engineering, Convex Computer Corporation, Richardson, Tx USA
References: 95-08-132
Date: Thu, 24 Aug 1995 18:57:33 GMT

>We are looking at creating some interpreter code, and we would
>>very much like to have the interpreter call into various methods and
>functions that we have available.
>The problem is that I have no idea how to build a proper stack frame
>on the fly (ie, interpreted code). In compiled code, this is no
>problem obviously.


>[In most cases, you need an assembler stub to make the stack frame for you.
>I've seen kludges with a big pile of indirect calls based on the number of
>arguments, but that's not reliable if args can be of different sizes. -John]


The kludges worked pretty well before the advent of screwy RISC
calling conventions, if you don't mind limiting yourself to some
finite # of arguments. you can't do it truly machine independently
since you are violating the C/C++ standard in principle.


Anyway, I'll elaborate on the kludges a little.


I am assumming that you have knowledge of the actual parameter types,
and that you have performed the necessary type conversions, and then
laid out the argument list in "arglist", paying attention to the
necessary alignments (or else that all args are of the same type)


Anyway, you have to know something about your calling convention: (In
any of these examples, since there are a finite # of types available,
you can switch about the return value type to retrieve it, if you
want it.)




1) in the old, "standard", calling conventions, args are in memory, and arg<n>
      is laid out 1 word after arg<n-1>


      int call(void (*func)(), int *arglist, int argWords) {
              (*func)(arglist[0], arglist[1], ... arglist[maxArgs]);
      }


2) same thing, but the calling convention requires that the # of args is
      pushd the stack by the caller:


      int call(void (*func)(), int *arglist, int argWords) {
              switch(argWords) {
                      case0: (*func)(); break;
                      case1: (*func)(arglist[0]);
...
                      case maxArgs: (*func)(arglist[0], arglist[1], ... arglist[maxArgs]);
      }


      if you know that you are going to only be passing args of a single type,
      this method is machine-independent.


3) say the first 2 floating point arguments and the first 2 int arguments
      are passed in fp and int registers, respectively. the rest get passed
      in memory.


      int call(void (*func)(), int intArg0, int intArg1, double fpArg0, double
          fpArg1, int *arglist, int argWords) {
              /* argWords does not include the first 2 int or fp args */
              switch(argWords) {
                      case0: (*func)(intArg0, intArg1, fpArg0, fpArg1); break;
                      case1: (*func)(intArg0, intArg1, fpArg0, fpArg1, arglist[0]);
...
                      case maxArgs: (*func)(intArg0, intArg1, fpArg0, fpArg1, arglist[0],
                            arglist[1], ... arglist[maxArgs]);
      }


some calling connventions lay out their memory arguments in reverse order,
i.e. arg<n> is the word before arg<n-1>. you need to take this into account
when laying out the arglist parameter that is passed into call(), because if
you have an large argument that requires m words, those words need to be
reversed when the function is invoked, i.e. if a double is two 4-byte words
and you are passing a single double parameter (arg), you need to say


        (*func)(*((int*)(((char *)&arg)+4)),*((int*)(&arg)))


and not


        (*func)(*((int*)((char *)&arg)),*((int*)(((char *)(&arg))+4)))


so lay out the arglist paramter in reverse,
then use a simple modification of the second case:


      int call(void (*func)(), int *arglist, int argWords) {
              switch(argWords) {
                      case0: (*func)(); break;
                      case1: (*func)(arglist[0]);
                      case1: (*func)(arglist[1], arglist[0]);
...
                      case maxArgs: (*func)(arglist[maxArgs], ... arglist[0]);
      }


anyway, you get the idea...


>How do I put "this" onto the callframe? My suspicion is that "this"
>is simply the first argument to any [non-static] method.


yes, it usually is.


also, some conventions require that, when returning certain data types
such as structs or long doubles, that the caller allocate the return
value area and pass a pointer to it as the first argument.


>I realize that I could create a class called CallFrame, which will
>bundle up arguments, and provide access methods, but I think that
>a true call is more elegant (avoids having to have a cover method
>which unbundles the arguments and then calls the real method).


yes, but a cover method which has knowledge of the parameter types
is about the only machine-independent way of doing it, unless your
language has a funcion similar to the lisp apply() function.


bb
--


Post a followup to this message

Return to the comp.compilers page.
Search the comp.compilers archives again.