16
$\begingroup$

This is more a question about compiler design than language design, but in low level languages, when a function is called, the parameters are pushed onto the stack, and when the function returns, the parameters are cleared from the stack.

As I understand this, one way that the stack is cleared is that the caller clears up the stack, where instructions to clear the parameters are inserted after the call instruction at the call site. Another way is that callee clears the parameters, where the last instructions of the function body are the instructions to clean up the parameters.

What are the reasons to choose one implementation over the other?

Inspired by this answer.

$\endgroup$
9
  • 2
  • 1
    $\begingroup$ @BruceAdams they're dupe votes $\endgroup$
    – Seggan
    Commented Jul 9, 2023 at 12:29
  • 2
    $\begingroup$ I see. Sometimes two very differently phrased questions can have the same answer. I don't think that makes the questions themselves duplicates. I reserve the close and duplicate vote for when the question is actually the same. $\endgroup$ Commented Jul 9, 2023 at 12:58
  • 1
    $\begingroup$ By "clearing the stack" do you mean only resetting the stack pointer and frame pointer registers or also something else? $\endgroup$
    – Bergi
    Commented Jul 9, 2023 at 14:04
  • 1
    $\begingroup$ Is it obvious what "clearing the stack" means here? Is it simply changing the stack pointer, or is there more to it than that? $\endgroup$ Commented Jul 12, 2023 at 13:02

2 Answers 2

16
$\begingroup$

Some advantages of "caller clears stack":

  • Depending on whether/how you have variadic parameters, the calling code would statically know how many arguments are present, whereas the callee would have to dynamically determine how many arguments to clear, which would cost a level of indirection
  • If the function has multiple exit points, then each "return" position is a simple ret (or equivalent) instruction, rather than a recitation of the cleanup sequence or a jmp to the cleanup block. This is effectively moot if you have a built-in "ret after clearing N bytes" instruction like x86 does

Some advantages of "callee clears stack":

  • Most functions are called more than once, so you'll have fewer overall instruction bytes
  • The callee can optimize exactly where the stack adjustment occurs, whereas the caller likely has fewer options of exactly where that instruction is placed
$\endgroup$
6
  • 1
    $\begingroup$ I wonder, do any languages switch between these? E.g., use caller-clears for cold functions and callee-clears for ones where it makes more sense? $\endgroup$
    – rydwolf
    Commented Jul 9, 2023 at 14:27
  • $\begingroup$ @RydwolfPrograms Languages like C using the Windows calling conventions can make a choice as to which convention to use on a function by function level (__stdcall, __cdecl, etc). It would be hard to do more dynamically unless the language doesn't support libraries - the library would have to have every possible convention. Doing it at runtime (JIT) would be expensive, because you would have to re-compile all of the caller functions to match the new convention. $\endgroup$
    – Cort Ammon
    Commented Jul 9, 2023 at 14:48
  • $\begingroup$ But wouldn't the callee have to discover how many variadic arguments there are, in order to use them productively, anyway? $\endgroup$ Commented Jul 9, 2023 at 19:00
  • $\begingroup$ @KarlKnechtel, the callee would see [return-address][size-of-arguments][arguments] starting at the stack-pointer address, and knowing the type-size of the arguments, it can calculate the number of variadic arguments. The callee would adjust the stack pointer only when calling another function. $\endgroup$ Commented Jul 10, 2023 at 13:26
  • 2
    $\begingroup$ @KarlKnechtel Not necessarily. See, for example, C's printf: I can write printf("hello %d\n", a, b, c); and printf only needs know it's been passed >1 int-sized argument. (Test-case: printf '#include <stdio.h>\nint main(int c){printf("hello %%d\\n",c,12,16,20);return 0;}' | tcc -run - a b c) $\endgroup$
    – wizzwizz4
    Commented Jul 10, 2023 at 13:35
6
$\begingroup$

Consider interrupts.

An interrupt can happen at any point of the program. The caller is not the one calling the interrupt - outside events cause the CPU to autonomously call the interrupt function with no action from the running program.

If the caller is responsible for saving and restoring state then correctly handling interrupts would be impossible since the program did not call the interrupt (also, most CPU designs will not save state for you with rare exceptions like Sun's Sparc register windows - even then, the hardware does not save all states).

You can of course design a runtime where the caller is responsible for saving and restoring state except for interrupt handlers - but this would require you to implement 2 types of functions: caller-save functions and callee-save functions. If you must implement callee-save functions why bother implement the caller-save mechanism?

$\endgroup$
1
  • 8
    $\begingroup$ ISR prologue and epilogue are usually different from normal function prologue and epilogue anyway, aren't they? $\endgroup$
    – Bergi
    Commented Jul 9, 2023 at 14:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .