How does the stack work, exactly?

I suspect this is a question I should already know the answer for, but I can't remember having read it (and I did a lot of reading I think).

When I did programming in Z80 or 8086 assembly, the stack was not very complicated. Anything you pushed to the stack or popped from the stack was the same size. Even when you pushed all registers with one command, that was just the same as pushing them one by one. For the programmer, or for the CPU, it was very clear how big the elements on the stack were. Just the size of a register.

In Rust the stack is used for data of different sizes, if I understand correctly. And that data is not just stored there, it is used while it remains on the stack. That implies in my mind that you need, just like in assembly, a base and a stack pointer, but you also need to have some mechanism for the compiled program to know the size of each element on the stack.

So I have two questions. First, do I understand this correctly? And second, if I do, how does the program know the size of each element on the stack?

Types. (I.e., it's not the program, it's the compiler.)

Yes, on a conceptual level I understand that. But in the end there is only machine code. In machine code there are no types. So how does the program translate this? Let me give an example: suppose we have three variables, a, b, c. The first has a type size of 8 bits, the second of 16 bits and the third again 8 bits. Their values in hexadecimal are: a=00, b=FFFF and c=EE. On a conceptual level the stack is easy:

a (byte)
b (word)
c (byte)

and a pointer to an element on the stack could move without a problem. But on the machine level this stack would be

00FFFFEE

What mechanism does Rust use to determine the right size of each element?

Again, I may be asking the wrong questions, and I apologize if I do. But I just can't wrap my mind around it

It doesn't. Again, the program doesn't know anything about this. It is the compiler that determines the stack size and puts each variable into an appropriately-sized (and aligned) place on the stack.

If you have three u8 variables that all need to be in scope at the same time, then the compiler statically determines, at compile-time, that you need a grand total of 3 bytes of stack space, and the variables will be placed at addresses SP, SP+1, SP+2 (or maybe an additional constant offset for other junk that function preludes/postludes need for saving registers etc.).

This also suggests to me that you are thinking that this layout procedure is something that happens dynamically, at runtime. It isn't.

2 Likes

The compiler calculates how much stack space is required, based on the sizes of the variables (determined by their types) and decides the relative address of each local variable that isn’t kept in a register. Then it generates code that simply moves the stack pointer up or down the required amount (traditionally the stack grows towards lower addresses so you subtract at function entry to allocate and add at function exit to deallocate).

2 Likes

As we say in Dutch, the quarter has fallen (the penny drops?). Thanks for the explanation, I understand now!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.