EABI

Overview

A vast majority of embedded software is developed in the C programming language.  The C programming language has many high level constructs that allow developers to quickly write software.  A C compiler translates C  code into the assembly language for a specific architecture.  In some cases, the code generated from the compiler many not be as efficient as is required by the design constraints of the product.  In this situation, it is not uncommon for a software engineer to develop portions of the software in assembly language to help improve the overall performance of an application.  Knowledge of the underlying processor architecture allows a developer to optimize a segment of code.

Functions that are written in assembly require parameters to be placed in specific locations defined by the function’s author.  Parameters can be placed in registers, the stack, or both.  When a C function calls an ARM assembly function, how does the compiler know where to place the parameters ?  The answer is that when writing assembly code, we must follow a standard set of rules for the placement of parameters if we want the ability to call assembly functions from C.  This set of rules is defined in the EABI.  The EABI is an acronym for Embedded Application Binary Interface.   We are going to focus on the specific portion of the EABI that defines parameter passing.

 Maintaining Registers

The main purpose of writing a routine in assembly code is to increase performance.  The EABI helps to increase performance by reducing the the number of registers that are PUSHed to and POPed from the stack.  Each register that is saved/restored to the stack requires a PUSH/POP operation.  Reducing the number of stack operations in a function will decrease the total number of clock cycles required to complete the function.

We have seen that there are two different approaches to saving registers.  Caller and callee saved registers.  Lets look at a couple of examples to see how each method has its faults.

;********************************************
; Function used for demonstration purposes
;********************************************
func1 PROC
    MOV     R1, 0x0F
    MOV     R2, 0xF0
    OR      R3, R1, R2
    MOV     R0, R3
    BX      LR
    ENDP

;********************************************
; main program start
;********************************************
__main PROC

    MOV     R4, #3
    MOV     R5, #4
    MOV     R6, #5
    PUSH    {R4-R6}
    BL      func1
    POP    {R4-R6}
    ADD     R0, R0, R4
    ADD     R0, R0, R5
    ADD     R0, R0, R6

    B        __main
    ENDP

The example above is a caller saved methodology.  Lets assume that the func1 and __main were developed by two independent developers who have no knowledge of what the registers are required to be maintained.  In a caller saved methodology, the caller needs to maintain the values of R4-R6 since R4-R6 are set before the the function call and their values are used after the function call.  Since __main has no insight on what registers func1 will modify, it must save these registers prior to the function call.  When we examine func1 itself, we see that the function does not modify the values contained in R4-R6.  In this case, there would be 6 unnecessary stack operations used to save and then restore R4-R6.

Let’s examine a caller saved methodology and see if that would help to reduce the number of stack operations

;********************************************
; Function used for demonstration purposes.
;********************************************
func1 PROC
    PUSH    {R1-R3}
    MOV     R1, 0x0F
    MOV     R2, 0xF0
    OR      R3, R1, R2
    MOV     R0, R3
    POP     {R1-R3}
    BX      LR
    ENDP

;********************************************
; main program start
;********************************************
__main PROC
    MOV     R4, #3
    MOV     R5, #4
    MOV     R6, #5
    BL      func1
    ADD     R0, R0, R4
    ADD     R0, R0, R5
    ADD     R0, R0, R6

    B        __main
    ENDP

Looking at the code above, we see that func1 modifies R1-R3.  func1 is required to save R1-R3 since it has no way of knowing if __main  uses those registers.  In this situation, __main does not use R1-R3, so this again results in 6 unnecessary stack operations.

The EABI dictates that saving/restoring registers to the stack is a shared responsibility of both the caller and callee.  The EABI requires that the caller save R0-R3.  R4-R12 must be maintained by the callee.  Using these rules, we can re-write the code above.

;********************************************
; Function used for demonstration purposes.
;********************************************
func1
    MOV     R1, 0x0F
    MOV     R2, 0xF0
    OR      R3, R1, R2
    MOV     R0, R3
    BX      LR

;********************************************
; main program start
;********************************************
__main
    MOV     R4, #3
    MOV     R5, #4
    MOV     R6, #5
    BL      func1
    ADD     R0, R0, R4
    ADD     R0, R0, R5
    ADD     R0, R0, R6

    B        __main

We see that by having the caller and callee share the responsibility of saving registers, that there are no stack operations necessary.

The idea that two developers would not know which registers are required to be saved may sound a little far fetched, but this scenario is exactly what would happen if you write the bulk of your application in C and call a hand coded assembly function.  How do you as the assembly writer know which registers need to be saved?  The answer is that if we write all of our assembly functions to be EABI compliant, then we are guaranteed that our hand written assembly will be compatible with code written in C.

Parameter Passing

Passing parameters to a function can take place in several different ways.  Parameters can be passed via registers, the stack, or even memory.   Without a standard, an assembly programmer could use any one of these approaches.  The EABI helps to sets a standard for how to pass parameters to a function.  This allows an application written in C to call a function written in assembly.  As long as both the assembler and the developer follow the same set of rules, both parties will know where to expect the parameters to be located.  The summary below will instruct you where to place parameters to ensure your function is EABI compliant.

EABI Summary

The following summary demonstrates how the EABI specifies which registers are saved by the caller and callee.  It also explains how parameters are passed.  There are other aspects of the EABI that are not taken into consideration.  Please see the the full EABI documentation for further details.

  1. The caller is required to save R0-R3.   Save only registers that contain data required for proper application execution.
  2. The callee is required to save R4-R12.  Save only the registers that are modified by the function.
  3. Any data that is returned by the function will be returned in R0
  4. The first 4 parameters to a function are passed in using registers R0-R3.  If there are more than 4 parameters, the additional parameters are passed on the stack.  Parameter 5 would be located at [SP + 0].  Parameter 6 would be located at [SP +4] and so on.