Stacks and Procedures
I forgot, am I Don’t know. But, if
the Caller you PUSH again I’m
or Callee? gonna POP you.
Language support for modular code is an integral part of modern computer
organization. In particular, support for subroutines, procedures, and functions.
09/24/2017 Comp 411 - Fall 2018 1
The Beauty of Procedures
● Reusable code fragments (modular design)
clear_screen();
… // code to draw a bunch of lines
clear_screen();
…
● Parameterized procedures (variable behaviors)
line(x1,y1,x2,y2,color); for (int i = 0; i < N-1; i++)
line(x2,y2,x3,y3,color); line(x[i],y[i],x[i+1],y[i+1],color);
line(x[i],y[i],x[0],y[0],color);
…
● Functions (procedures that return values)
xMax = max(max(x1,x2),x3);
yMax = max(max(y1,y2),y3);
09/24/2017 Comp 411 - Fall 2018 2
More Procedure Power
● Global vs. Local scope (Name Independence)
int x = 9;
These are different “x”s
int fee(int x) { How do we
return x+x-1; keep track of
}
int foo(int i) {
all these
int x = 0; This is yet another “x” variables?
while (i > 0) {
x = x + fee(i);
i = i - 1;
}
return x;
} That “fee( )” seems odd to me?
main() { And, foo( )’s a bit square.
fee(foo(x));
}
09/24/2017 Comp 411 - Fall 2018 3
Using Procedures
● A “calling” program (Caller) must:
– Provide the procedure’s parameters. In other words, put arguments
in a place where the procedure can access them
– Transfer control to the procedure.
“Branch” to it, and provide a “link” back
● A “called” procedure (Callee) must:
– Acquire/create resources needed to perform the function
(local variables, registers, etc.)
– Perform the function
– Place results in a place where the Caller can find them
– Return control back to the Caller through the supplied link
● Solution (a least a partial one):
– WE NEED CONVENTIONS, agreed upon standards for how arguments
are passed in and how function results are retrieved
– Solution part #1: Allocate registers for these specific functions
09/24/2017 Comp 411 - Fall 2018 4
ARM Register Usage
Recall these conventions from last time
● Conventions designate Register Use
registers for procedure R0-R3 First 4 procedure arguments.
arguments (R0-R3) and Return values are placed in R0 and R1.
return values (R0-R3). R4-R10 Saved registers. Must save before using
● The ISA designates a and restore before returning.
“linkage pointer” for R11 FP - Frame pointer
calling procedures (R14) (to access a procedure’s local variables)
● Transfer control to R12 IP - Temp register used by assembler
Callee using the BL
R13 SP - Stack pointer
instruction Points to next available word
● Return to Caller with R14 LR - Link Register (return address)
the BX LR instruction
R15 PC - program counter
09/24/2017 Comp 411 - Fall 2018 5
And it almost works!
x: .word 9 Works for cases where Callees
need few resources and call no
other functions.
Callee
fee: ADD R0,R0,R0 The “BX” instruction
This type of function (one that calls
ADD R0,R0,#1 changes the PC to the
contents of the no other) is called a LEAF function.
BX LR specified register.
Here it is used to
return to the address But there are still a few issues:
after the one where
“fee” was called. How does a Callee call functions?
Caller More than 4 arguments?
main: LDR R0,x Recall that when the “L”
suffix is appended to a Local variables?
BL fee branch instruction, it
causes the address of Where does main return to?
BX LR the next instruction to
be saved in the “linkage
register”, LR.
Let’s consider the worst case of a
Callee who is a Caller...
09/24/2017 Comp 411 - Fall 2018 6
Callees who call themself!
int sqr(int x) {
if (x > 1) How do we go about writing
x = sqr(x-1)+x+x-1; non-leaf procedures?
return x; Procedures that call other
}
procedures, perhaps even
themselves.
main()
{ sqr(10) = sqr(9)+10+10-1 = 100
sqr(10); sqr(9) = sqr(8)+9+9-1 = 81
} sqr(8) = sqr(7)+8+8-1 = 64
Oh, recursion sqr(7) = sqr(6)+7+7-1 = 49
gives me a sqr(6) = sqr(5)+6+6-1 = 36
headache. sqr(5) = sqr(4)+5+5-1 = 25
sqr(4) = sqr(3)+4+4-1 = 16
sqr(3) = sqr(2)+3+3-1 = 9
sqr(2) = sqr(1)+2+2-1 = 4
sqr(1) = 1
sqr(0) = 0
09/24/2017 Comp 411 - Fall 2018 7
A First Try OOPS!
int sqr(int x) { sqr: CMP R0,#1
if (x > 1) BLE return
x = sqr(x-1)+x+x-1; R4 is clobbered MOV R4,R0
return x; on successive SUB R0,R0,#1
} calls. BL SQR
ADD R0,R0,R4
ADD R0,R0,R4
main() SUB R0,R0,#1 We also
{ return: BX LR clobber our
sqr(10); return
address, so
} main: MOV R0,#10 there’s no
BL sqr way back!
BX LR
Will saving “x” in memory rather than in a register help?
i.e. replace MOV R4,R0 with STR R0,x and adding LDR R4,x after BL SQR
09/24/2017 Comp 411 - Fall 2018 8
A Procedure’s Storage Needs
● In addition to a conventions for using registers to pass in arguments
and return results, we also need a means for allocating new
variables for each instance when a procedure is called.
The “Local variables” of the Callee:
...
{
int x, y;
... x ... y ...;
}
● Local variables are specific to a “particular” invocation or activation
of the Callee. Collectively, the arguments passed in, the return
address, and the callee’s local variables are its
activation record, or call frame.
09/24/2017 Comp 411 - Fall 2018 9
Lives of Activation Records
int sqr(int x) {
if (x > 1) Where are activation
records stored?
x = sqr(x-1)+x+x-1;
return x;
}
TIME
sqr(3) sqr(3) sqr(3) sqr(3) sqr(3)
sqr(2) sqr(2) sqr(2)
sqr(1)
Each call of sqr(x) has a different notion of
what “x” is, and a different place to return to.
A procedure call creates a new Return to previous activation record
activation record. Caller’s record when procedure finishes, permanently
is preserved because we’ll need it discarding activation record created by
when call finally returns. call we are returning from.
09/24/2017 Comp 411 - Fall 2018 10
We need dynamic storage!
What we need is a Some interesting
SCRATCH memory for properties of
holding temporary variables. stacks:
We’d like for this memory
to grow and shrink as SMALL OVERHEAD.
needed. And, we’d like it to Everything is
have an easy management referenced relative
policy. to the top, the
so-called
“top-of-stack”
One possibility is a
STACK Add things by
PUSHING new values
on top.
A last-in-first-out (LIFO)
data structure. Remove things by
POPPING off values.
09/24/2017 Comp 411 - Fall 2018 11
ARM Stack Convention
CONVENTIONS: Higher
03FFFFFC
• Dedicate a register for addresses
“stack” segment
the Stack Pointer
(SP = 13). SP
• Stack grows DOWN of the stack?
(towards lower addresses) is that the TOP
on pushes and allocates
Humm… Why
• SP points to the last or
TOP *used* location.
“text” segment
• Stack is placed far away 0000000816
(Program)
from the program
Reserved
and its data. Lower
addresses
09/24/2017 Comp 411 - Fall 2018 12
Turbo Stack InstrucTions
Recall ARM’s block move instructions LDMFD and STMFD which are
ideal for implementing our stack. The “M” means multiple, the “F” means
full (i.e. the SP points to the last pushed entry, as opposed to “E” for
empty, the next available entry), and the “D” stands for descending
(growing towards lower addresses, vs. “A” for ascending).
STMFD SP!,{r4,r7,LR} LRMFD SP!,{r4,r7,LR}
Regardless of order that
increasing registers appear in the set, they
increasing
addresses <used> are always saved in order of addresses <used>
largest to smalleset
<used> <used>
<used> Initial SP <used> Final SP
The specified registers
<free>
LR LR are loaded and the SP is
changed, but the copy in
<free>
R7 R7 memory remains
<free>
R4 Final SP R4 Initial SP
<free> <free>
<free> <free>
09/24/2017 Comp 411 - Fall 2018 13
Incorporating A StaCK
int sqr(int x) { sqr: STMFD SP!,{R4,LR}
if (x > 1) CMP R0,#1
x = sqr(x-1)+x+x-1; BLE return
return x; MOV R4,R0
} SUB R0,R0,#1
BL SQR
ADD R0,R0,R4
main() ADD R0,R0,R4
{ SUB R0,R0,#1
sqr(10); return: LRMFD SP!,{R4,LR}
} BX LR
main: MOV R0,#10
BL sqr
BX LR
09/24/2017 Comp 411 - Fall 2018 14
Revisiting Factorial
int fact(x) { main: ldr r0,x
if (x <= 1) bl fact miniARM
return x; str r0,y
else bx lr
return x*fact(x-1);
} x: .word 5
y: .word 0
int x = 5;
int y;
fact: stmfd sp!,{r4,lr}
y = fact(x); cmp r0,#1
ble return
It works! And the changes are mov r4,r0
relatively small. Just saving r4 sub r0,r0,#1
and lp on entry, and replacing
them before returning bl fact
mul r0,r0,r4
return: ldmfd sp!,{r4,lr}
bx lr
09/24/2017 Comp 411 - Fall 2018 15
Missing Details
Thus far the stack has been only been used by callee’s that are also
callers (i.e. non-leaf procedures) to save resources that “they” and
“their caller” expect to be preserved.
Our procedure calling convention works, but it has a few limitations...
1. Callee’s are limited to 4 arguments
2. All arguments must “fit” into a single register
3. What if our argument is not a “value”,
but instead, an address of where to
put a result (i.e. an array, an object, etc.)
09/24/2017 Comp 411 - Fall 2018 16
CallER provided Storage
If a caller calls a function that requires more than 4 arguments, it
must place these extra arguments on the stack, and remove them
when the callee returns.
sum6: add r1,r0,r1 ; b=a+b
add r1,r1,r2 ; b=b+c
add r1,r1,r3 ; b=b+d
int sum6(int a, int b, int c, int d, int e, int f) { ldr r2,[sp, #0]
return a+b+c+d+e+f; add r0,r1,r2 ; a=b+e
} ldr r2,[sp, #4]
add r0,r0,r2 ; a=a+f
int main() { bx lr
return sum6(2,3,4,5,6,7);
} main: sub sp,sp,#8 ; allocate extra args
mov r3,#6
<used> str r3,[sp,#0]
SP → mov r3,#7
<used> str r3,[sp,#4]
R0: 2 mov r0,#2
<free>
7
mov r1,#3
R1: 3 SP-8 → mov r2,#4
<free>
6
R3: 4 mov r3,#5
R4: 5 <free> bl sum6
add sp,sp,#8
<free> halt: b halt
09/24/2017 Comp 411 - Fall 2018 17
Complex Arguments
How do we pass arguments that don’t fit in a register?
- Arrays
- Objects
- Dictionaries
- etc.
Rather than copy the complex arguments, we instead just send an
“address” of where the complex argument is in memory.
Conundrum: Callees process “copies” of simple arguments, and thus
any modifications they make don’t affect the original. But, with
complex arguments, the callee modifies the original version.
09/24/2017 Comp 411 - Fall 2018 18
Next time
Special variable types for holding “addresses”
1. Pointers
2. Dereferencing
3. Addresses of pointers
09/24/2017 Comp 411 - Fall 2018 19