High level language to Assembly

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.


stack frame

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