Even though most of Embedded and Realtime programming is now carried out in
high level languages, a good understanding of the generated assembly code really
helps in debugging, performance analysis and performance tuning.
Here we present a series of articles describing C to assembly translation. We
will be analyzing the code generated by a compiler targeting the Motorola 68000
processor family. The concepts learnt here can easily be applied to understand
the generated code for any other processor-compiler combination.
In this article, we will discuss the assembly code generated for function
calling, parameter passing and local variable management. Before we go any
further we need to discuss a few things about the 68000 architecture.
68000 Basics
- 68000 family processors contain 8 data registers (D0-D7) and 8 address
registers (A0-A7).
- The MOVE instruction in 68000 assemblers has the source on the left
side and destination on the right side.
- Register D0 is used to return values to the calling function.
- The stack in the 68000 family grows from higher address to lower address.
Thus a push results in a decrement to the stack pointer. A pop results in an
increment to the stack pointer.
- Address register A7 is the stack pointer. The pre-decrement (i.e.
decrement the register before use) addressing mode is used to implement a
push. The post-increment addressing mode (i.e. use register and then
increment) is used to implement a pop.
- Most compilers use A6 as the frame pointer. The frame pointer serves as an
anchor between the called and the calling function.
- When a function is called, the function first saves the current value of
A6 on the stack. It then saves the value of the stack pointer in A6 and then
decrements the stack pointer to allocate space for local variables.
- The frame pointer (A6) is used to access local variables and parameters.
Local variables are located at a negative offset to the frame pointer.
Parameters passed to the function are located at a positive offset to the
frame pointer.
- When the function returns, the frame pointer (A6) is copied into the stack
pointer. This frees up the stack used for local variables. The value of A6
saved on the stack is restored. (This is the frame pointer for the function
that called this function).
Function Calling
The following block shows the C code and the corresponding generated assembly
code.
C
Code |
int CallingFunction(int x) { int y; CalledFunction(1,2); return (5); }
void CalledFunction(int param1, int param2) { int local1, local2; local1 = param2; }
|
The generated assembly code is shown along with the corresponding C
code.
Generated
68000 Assembly Code |
int CallingFunction(int x) { int y; * Reserving space for local variable y (4 bytes) LINK A6, #-4 CalledFunction(1,2); * Pushing the second parameter on the stack MOVE.L #2, -(A7) * Pushing the first parameter on the stack MOVE.L #1, -(A7) * Calling the CalledFunction() JSR _CalledFunction * Pop out the parameters after return ADDQ.L #8, A7 return (5); * Copy the returned value 5 into D0 (As a convention, D0 is used to pass * return values) MOVEQ.L #5, D0 } * Freeing up the stack space taken by local variables UNLK A6 * Return back to the calling function RTS
void CalledFunction(int param1, int param2) { int local1, local2; * Reserving space for local1, local2 (4 bytes each) LINK A6, #-8 local1 = param2; MOVE.L 12(A6), -4(A6) } * Freeing up the stack space taken by local variables UNLK A6 * Return back to the calling function RTS
|
Function Calling Sequence
The generated assembly code is best understood by tracing through the
invocation of CalledFunction() from CallingFunction().
- CallingFunction() pushes values 2 followed by 1 on the stack. These values
correspond to param2 and param1 respectively. (Note that pushing order is
reverse of the declaration order.). This is implemented by the MOVE.L
instructions that implement a push operation by using the predecrement mode
on A7.
- CallingFunction() invokes the CalledFunction() by the JSR instruction. JSR
pushes the return address on the stack and transfers control to
CalledFunction().
- Called Function executes the LINK instruction. The link instruction
performs the following operations:
- Saves the CallingFunction()'s frame pointer on the stack (i.e. content
of A6 is saved on the stack)
- Moves the contents on the stack pointer (A7) into A6. This will serve
as the frame pointer for CalledFunction().
- Decrements the stack pointer by 8 to create space for the local
variables local1 and local2.
- After the LINK instruction, code in the CalledFunction() accesses passed
parameters by taking positive offsets from the frame pointer. Local
variables are accessed by taking negative offsets from the frame pointer.
(The above assembly code gives one example of such an access).
- Before the function ends UNLK is executed to undo the actions taken by the
LINK instruction. This is done in the following steps:
- Copy the contents of A6 to A7. This will free the stack entries
allocated for local variables local1 and local2.
- Pop the saved frame pointer from the stack. (This will make sure that
the CallingFunction() gets its original frame pointer value on return).
- The processor now executes the RTS instruction. This instruction pops the
return address from the stack and transfers control to the CallingFunction()
at this address.
- The CallingFunction() now pops the parameters that were passed to the
CalledFunction(). This is done by adding 8 to the stack pointer.
CallingFunction() Stack Snapshot
The figure below shows the stack snapshot when executing CalledFunction()
body, i.e. after the LINK instruction has been executed, but before the UNLK
instruction has been executed.
阅读(1727) | 评论(0) | 转发(0) |