Runtime environment
Runtime stack (activation record)
In this lecture we will discuss how a high level language being compiled
into assembly language. One of the necessary supports for function call in
high level language is the runtime environment. It is simply called
"stack frame" or "frame" in short. Sequences of assembly code must
be added to the main code to "create" this frame when "entering" the
function body and "delete" it when the function call is ended. A
frame is stored in the main memory, with a global register "FP"
(frame pointer) points to it. The frame is used to save values of
registers of the caller when entering the function body and restore them
when ending (return). The registers that need to be saved/restored are the
ones use by the function. This includes the link register that holds
"return address" because function call can be nested (call another
function).
Let us see the simplest program compiled into assembly
main()
a = 0
the output is: (ignore the symbol/data section)
...
.code 0
mov fp #3500
mov sp #3000
jal rads main ; *** call main
trap r0 #0
; *** stop
:main
st r1 @1 fp
add fp fp #2
st rads @0 fp
mov r1 #0 ; <<<
*** a = 0
:L101
ld rads @0 fp
sub fp fp #2
ld r1 @1 fp
ret rads
.end
Note: to access frame, we use addressing with offset (disp)
ld r1 @d r2 R[r1] = M[ R[r2] + d]
st r1 @d r2 M[ R[r2] + d] = R[r1]
The runtime stack is created.
:main
st r1 @1 fp
add fp fp #2
st rads @0 fp ; *** create
frame
<body>
ld rads @0 fp
sub fp fp #2
ld r1 @1 fp
; *** delete frame
ret rads
when a function is called, it creates a "frame" to store the "return
point" and local variables.
Figure 1 Picture of stack frame when main() call A() and A() call B().
in the above example, fp points to the current frame, the return point
(rads) is stored at the slot 0. local var r1 ("a") is stored at the slot
1.
a frame looks like this
memory
stack grows downward
----
<- fp'
r1
fp-1 (main)
retads fp+0 <- fp
-----
let us see a call to a user defined function
double(x)
return x + x
main()
a = double(2)
its assembly language is:
.code 0
:double
st r1 @1 fp
st r2 @2 fp
add fp fp #3
st rads @0 fp
pop sp r1 ; *** get actual parameter
x
add r2 r1 r1 ; *** tmp = x + x
mov retval r2 ; *** return tmp
...
ld rads @0 fp
sub fp fp #3
ld r2 @2 fp
ld r1 @1 fp ; *** delete frame
ret rads
:main
...
mov r2 #2
push sp r2 ; *** pass x to double
jal rads double
....
ret rads
.end
double() has two local vars r1 (x) and r2 (tmp). it accepts the passing
value (x) from parameter stack. The parameter stack is a separate stack
used to keep the passing parameters when doing a function call. A global
register ("SP", for stack pointer) points to this region. The values
of FP and SP must be initialized when main() starts.
frames look like this:
----
r1
(main)
retads
-----
r1
fp-2 (double)
r2 fp-1
retads fp+0 <- fp
-----
Now let us see how to access an array
ax[10]
main()
ax[1] = 22
its assembly is:
.symbol
ax 1100 ; *** compiler
allocate ax at 1100
.code 0
:main
...
mov r1 #22
mov r2 #1
st r1 @ax r2 ; *** ax[1] = 22
...
.end
You can use my compiler "rz36" to compile a high level language file (Rz)
into S2 assembly language and inspect the output file.
test(x)
if( x == 0 )
b = 1
else
b = 2
while( b < 10 )
b = b + 1
return b
main()
test(x)
use the command in a console :
> rz36 test.txt > out.txt
out.txt contains the assembly file. You can assemble and run it.
> as21 out.txt
this will produce two files: "out.lis" and "out.obj". You run "out.obj" using the simulator
> sim21 out.obj
end
last update 31 Mar 2020