SNIU028D February 2016 – September 2020 UCD3138 , UCD3138064 , UCD3138064A , UCD3138128 , UCD3138A , UCD3138A64
Addresses of functions taken in 16-BIS state use the address of the 16-BIS state entry point to the function (with bit 0 of the address set). Likewise, addresses of functions taken in 32-BIS state use the address of the 32-BIS state entry point (with bit 0 of the address cleared).
Then all indirect calls are performed by loading the address of the called function into a register and executing the branch and exchange (BX) instruction. This automatically changes the state and ensures that the code works correctly, regardless of what state the address was in when it was taken.
The return address must also be set up so that the state of the processor is consistent and known upon return. Bit 0 of the address is tested to determine if the BX instruction invokes a state change. If it does not invoke a state change, the return address is set up for the state of the function. If it does invoke a change, the return address is set up for the alternate state and code is executed to return to the function’s state. Because the entry point into a function depends upon the state of the function that takes the address, it is more efficient to take the address of a function when in the same state as that function.
This ensures that the address of the actual function is used, not its alternate entry point. Because the indirect call can invoke a state change itself, entering a function through its alternate entry point, even if calling it from a different state, is unnecessary.
Example-1 shows sum( ) calling max( ) with code that is compiled for the 16-BIS state and supports dual-state interworking.
The sum( ) function is compiled with the −mt option, which creates 16-bit instructions. Example-2 shows the same function call with code that is compiled for the 32-BIS state and supports dual-state interworking. Function max( ) is compiled without the −mt option, creating 32-bit instructions.
Example-1. Code Compiled for 16-BIS State: Sum( )
int total = 0;
sum(int val1, int val2)
{
int val = max(val1, val2);
total += val;
}
;*****************************************************************
; function venner: _sum *
;*****************************************************************
_sum:
.state32
STMFD sp!, {lr}
ADD lr, pc, #1
BX lr
.state16
BL $sum
BX pc
NOP
.state16
.sect ".text"
.global $sum
;*****************************************************************
; function def: $sum *
;*****************************************************************
$sum:
PUSH {LR}
BL $max
LDR A2, CON1
LDR A3, [A2, #0]
ADD A1, A1, A3
STR A1, [A2, #0]
POP {PC}
;*****************************************************************
; constant table *
;*****************************************************************
sect".text"
.align4
Example-2. Code Compiled for 32-BIS State: max( )
int max(int x,int y)
{
return (x < y ? y : x):
}
;*****************************************************************
; function venner: $max *
;*****************************************************************
$max:
.state16
BX pc
NOP
.state32
B _max
.text
.global _max
;*****************************************************************
; function def: _max *
;*****************************************************************
_max:
CMP A1, A2
MOVLE A1, A2
BX LR
Since sum( ) is a 16-bit function, its entry point is $sum. Because it was compiled for dual-state interworking, an alternate entry point, _sum, located in a different section is included. All calls to sum( ) requiring a state change use the _sum entry point.
The call to max( ) in sum( ) references $max, because sum( ) is a 16-bit function. If max( ) were a 16-bit function, sum( ) would call the actual entry point for max( ).
However, since max( ) is a 32-bit function, $max is the alternate entry point for max( ) and handles the state change required by sum( ).
Note that the above description applies for CCS 3 up to CCS 3.3.38.
With CCS 6.x, the object code will have the same structure, but the functions all have underscore as their first character whether they are in ARM or Thumb mode. The assembler knows the mode by the setting at assembly time. This information is stored in the object file, and the linker actually inserts the code for mode switching. It is no longer compiled around each function as in 3.3.