6.4.3. Returning non integer-like structures in registers

There are occasions when a function must return more than one value. The usual way to achieve this is to define a structure that holds all the values to be returned, and to pass a pointer to the structure back in a1. The pointer is then dereferenced, allowing the values to be stored.

For applications in which such a function is time-critical, the overhead involved in wrapping and then unwrapping the structure can be significant. In this case, you can tell the compiler that a structure should be returned in the argument registers a1 - a4, by using the keyword __value_in_regs.

This is only useful for returning structures that are no larger than four words.

Returning a 64-bit result

To illustrate how to use __value_in_regs, consider a function that multiplies two 32-bit integers together and returns a 64-bit result.

To make such a function work, you must split the two 32-bit numbers (a, b) into high and low 16-bit parts (a_hi, a_lo, b_hi, b_lo). You then perform the four multiplications a_lo * b_lo, a_hi * b_lo, a_lo * b_hi, a_hi * b_lo and add the results together, taking care to deal with carry correctly.

Since the problem involves manipulation of the Carry flag, writing this function in C does not produce optimal code (see An example of APCS register usage: 64-bit integer addition). Therefore you must code the function in ARM assembly language. Example 6.9 shows code that implements the algorithm.

Example 6.9. 

; On entry a1 and a2 contain the 32-bit integers to be multiplied (a, b)
; On exit a1 and a2 contain the result (a1 bits 0-31, a2 bits 32-63) 
	MOV		ip, a1, LSR #16					; ip = a_hi
	MOV		a4, a2, LSR #16					; a4 = b_hi
	BIC		a1, a1, ip, LSL #16					; a1 = a_lo
	BIC		a2, a2, a4, LSL #16					; a2 = b_lo
	MUL		a3, a1, a2					; a3 = a_lo * b_lo		(m_lo)
	MUL		a2, ip, a2   			 		; a2 = a_hi * b_lo		(m_mid1)
	MUL		a1, a4, a1					; a1 = a_lo * b_hi		(m_mid2)
	MUL		a4, ip, a4					; a4 = a_hi * b_hi		(m_hi)
	ADDS		ip, a2, a1					; ip = m_mid1 + m_mid2			(m_mid)
	ADDCS		a4, a4, #&10000					; a4 = m_hi + carry		(m_hi')
	ADDS		a1, a3, ip, LSL #16					; a1 = m_lo + (m_mid<<16)
	ADC		a2, a4, ip, LSR #16					; a2 = m_hi' + (m_mid>>16) + carry
	MOV		pc, lr


On processors with a fast multiply unit such as the ARM7TDMI and ARM7DMI this example can be recoded using the UMULL instructions.

Example 6.9 is fine for use with assembly language modules, but to use it from C you must tell the compiler that this routine returns its 64-bit result in registers. You can do this by making the following declarations in a header file.

typedef struct int64_struct
	unsigned int lo;
	unsigned int hi;
__value_in_regs extern int64 mul64(unsigned a, unsigned b);

The above assembly language code and declarations, together with a test program, are in the directory examples/candasm as the files mul64.s, mul64.h, int64.h and multest.c.

To compile, assemble, and link these to produce an executable image suitable for armsd, copy them to your current directory, and then execute the following commands:

armasm mul64.s -o mul64.o -li
armcc -c multest.c -li
armlink mul64.o multest.o -o multest

where -li can be omitted if armcc and armasm (and armsd, below) have been configured with it as a default.

Follow these step to run multest under armsd:

  1. Enter armsd -li multest to load the image into armsd. The armsd prompt is displayed:


  2. Type go at the armsd prompt to run the program. The following line is displayed:

    Enter two unsigned 32-bit numbers in hex eg.(100 FF43D)

  3. Type 12345678 10000001

    The following lines are displayed:

    Least significant word of result is 92345678
    Most  significant word of result is  1234567
    Program terminated normally at PC = 0x00008418
          0x00008418: 0xef000011 .... : >  swi     Angel

  4. Type quit at the armsd prompt to exit armsd.

To confirm that __value_in_regs is being used, remove it from mul64.h, recompile multest.c, relink multest, and rerun armsd. This time the answers returned will be incorrect, because the result is no longer being returned in registers, but in a block of memory.

Copyright © 1997, 1998 ARM Limited. All rights reserved.ARM DUI 0040D