It's not possible to implement function call semantics without using some sort of stack. It's only possible to play word games (e.g. use a different name for it, like "FILO return buffer").
It is possible to use something that doesn't implement function call semantics (e.g. continuation passing style, actors), and then build function call semantics on top of it; but this means adding some sort of data structure to track where control is passed when the function returns, and that data structure would be a type of stack (or a stack with a different name/description).
Imagine you have many functions that can all call each other. At run-time, each function must know where to return to when the function exits. If first
calls second
then you have:
second returns to somewhere in first
Then, if second
calls third
you have:
third returns to somewhere in second
second returns to somewhere in first
Then, if third
calls fourth
you have:
fourth returns to somewhere in third
third returns to somewhere in second
second returns to somewhere in first
As each function is called, more "where to return" information must be stored somewhere.
If a function returns, then its "where to return" information is used and is no longer needed. For example, if fourth
returns back to somewhere in third
then the amount of "where to return to" information would become:
third returns to somewhere in second
second returns to somewhere in first
Basically; "function call semantics" implies that:
- you must have "where to return" information
- the amount of information grows as functions are called and is reduced when functions return
- the first piece of "where to return" information stored will be the last piece of "where to return" information discarded
This describes a FILO/LIFO buffer or a stack.
If you attempt to use a type of tree, then every node in the tree will never have more than one child. Note: a node with multiple children can only happen if a function calls 2 or more functions at the same time, which requires some sort of concurrency (e.g. threads, fork(), etc) and it would not be "function call semantics". If every node in the tree will never have more than one child; then that "tree" would only be used as a FILO/LIFO buffer or a stack; and because it's only used as a FILO/LIFO buffer or a stack it's fair to claim that the "tree" is a stack (and the only difference is word games and/or implementation details).
The same applies to any other data structure that could conceivably used to implement "function call semantics" - it will be used as a stack (and the only difference is word games and/or implementation details); unless it breaks "function call semantics". Note: I would provide examples for other data structures if I could, but I can't think of any other structure that is slightly plausible.
Of course how a stack is implemented is an implementation detail. It could be an area of memory (where you keep track of a "current stack top"), it could be some sort of linked list (where you keep track of "current entry in list"), or it could be implemented in some other way. It also doesn't matter if hardware has built-in support or not.
Note: If only one invocation of any procedure may be active at any moment; then you can statically allocate space for "where to return to" information. This is still a stack (e.g. a linked list of statically allocated entries used in a FILO/LIFO way).
Also note that there are some things that do not follow "function call semantics". These things include "potentially very different semantics" (e.g. continuation passing, actor model); and also includes common extensions to "function call semantics" like concurrency (threads, fibres, whatever), setjmp
/longjmp
, exception handling, etc.
realloc()
.