User: Password:
Subscribe / Log in / New account

4K stacks by default?

4K stacks by default?

Posted Apr 25, 2008 4:22 UTC (Fri) by jzbiciak (subscriber, #5246)
In reply to: 4K stacks by default? by giraffedata
Parent article: 4K stacks by default?


If parameters are passed on the stack, the argument frame basically exists on the stack for the entire duration of the function. If those same arguments are passed in registers, the arguments exist only as long as they're needed. If they're unused, consumed before a funtion call or passed down the call chain, they don't need to go to the stack.

The only things that need to go on the stack as you go down the call chain are values that are live across the call that don't have other storage--compiler temps and arguments are used after the call.

I haven't looked at the document linked above, but I wouldn't be surprised if the x86-64 calling convention also splits the GPRs between caller-saves vs. callee-saves, thereby also reducing the number of slots reserved for values live-across calls.

Separate of compiler temps and live-across call values are spill values. In my experience, modern compilers allocate a stack frame once at the start of a function and maintain it through the hlife of the function (alloca() being a notable exception, allocating beyond the static frame). If a function has a lot of spilled values, these too get statically allocated. x86 has less than half as many general purpose registers as x86-64, resulting in greater numbers of spilled variables as well.

Make sense?

How about an example? Here's the function prolog from ay8910_write in my Intellivision emulator, compiled for x86:

    subl    $60, %esp   #,

The function allocates a 60 byte stack frame for itself, in addition to 12 bytes for arguments 2 through 4. (Only the first argument gets passed in a register as I recall). That's 72 bytes. Here's the same function prolog on x86-64:

    movq    %r13, -24(%rsp) #,
    movq    %r14, -16(%rsp) #,
    movq    %rdi, %r13  # bus, bus
    movq    %r15, -8(%rsp)  #,
    movq    %rbx, -48(%rsp) #,
    movl    %edx, %r15d # addr, addr
    movq    %rbp, -40(%rsp) #,
    movq    %r12, -32(%rsp) #,
    subq    $56, %rsp   #,

This version allocated 56 bytes, and had all its arguments passed in registers. That's 16 bytes smaller.

I picked this function not because it's some extraordinary function, but rather because it's moderately sized with a moderate number of arguments, and it's smack dab in the middle of a call chain. And it's in production code.

(Log in to post comments)

4K stacks by default?

Posted Apr 25, 2008 17:41 UTC (Fri) by NAR (subscriber, #1313) [Link]

That's interesting. I thought that the local variables are stored also on the stack and if you
have pointers or integers which are bigger on x86-64, than the storage needed for these
variables on the stack are also bigger. Of course, the clever compiler can optimize these
variables to registers...

4K stacks by default?

Posted Apr 25, 2008 20:39 UTC (Fri) by nix (subscriber, #2304) [Link]

Generally, even if locals live in registers they'll get stack slots 
assigned, because you have to store the locals somewhere across function 
calls. (Completely trivial leaf functions with almost no variables *might* 
be able to get away without it, but that's not the common case.)

4K stacks by default?

Posted Apr 25, 2008 21:12 UTC (Fri) by jzbiciak (subscriber, #5246) [Link]

They should only *need* to get stored if

1. They're live-across-call and there are no callee-save registers to park the values in.
2. They get spilled due to register pressure.
3. Their address gets taken.
4. Their storage class requires storing to memory (e.g. volatile).

And there could be other reasons where it *might* end up on the stack, such as:

5. The compiler isn't able to register allocate the type--this happens most often with
6. Compilation / debug model needs it on the stack.
7. Cost model for the architecture suggests register allocation for the variable isn't a win.

#1 above is actually pretty powerful.  Texas Instruments' C6400 DSP architecture has 10
registers that are callee-save and the first 10 arguments of function calls are passed in
registers.  The CPU has 64 registers total.  All these work together to absorb and eliminate
quite a bit of stack traffic on that architecture.  

I'm less familiar w/ GCC, the x86 and x86-64 ABIs and how they work, which prompted my
original question.

4K stacks by default?

Posted Apr 25, 2008 21:29 UTC (Fri) by jzbiciak (subscriber, #5246) [Link]

In that last bit of comment, I should say "the notion of having some number of callee-save
registers" is pretty powerful.  If a function doesn't use very many registers, it may never
have to touch the callee-save registers.  If a caller only has a handful of live-across-call
variables, it may be able to fit them entirely into callee-save registers.  

This limits stack traffic in the body of the function dramatically, causing some additional
traffic at the edges of the mid-level function to save/restore the callee-save registers.
Those save/restore sequences tend to be fairly independent of the rest of the code, too, which
works well on dynamically scheduled CPUs.

Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds