Loading constants into registers


About this recipe

This recipe explains and demonstrates:

Why is loading constants an issue?

Since all ARM instructions are precisely 32 bits long, and ARM instructions do not use the instruction stream as data, there is no single instruction which will load any 32 bit immediate constant into a register without performing a data load from memory.

However, there are ways to load many commonly used constants into a register without resorting to a data load from memory. Of course, a data load from memory allows any 32-bit value to be loaded into a register, but the added expense of a data load can often be avoided.

The assembler provides several 'instruction extensions', and two pseudo instructions to make the efficient loading of constants and addresses non-painful.

MOV / MVN

As described in the recipe Using the Barrel Shifter, the MOV and MVN instructions allow many constants to be constructed. The constants which these instructions can construct must be eight bit constants rotated right through an even number of positions. By using MVN the bitwise complement of such values can also be constructed.

Having to convert a constant into this form is an onerous task no-one wants to do. Therefore armasm will do this automatically. Either MOV or MVN may be used with a constant which can be constructed using either of these instructions. armasm will choose the correct instruction and construct the constant. If it is impossible to construct the desired constant armasm will report this as an error.

To illustrate this, look at the following MOV and MVN instructions. The instruction listed in the comment is the ARM instruction which is produced by armasm.

MOV R0, #0                                 ; => MOV R0, #0
MOV R1, #&FF000000                         ; => MOV R1, #&FF, 8 
MOV R2, #&FFFFFFFF                         ; => MVN R2, #0
MVN R0, #1                                 ; => MVN R0, #1
MOV R1, #&FC000003                         ; => MOV R1, #&FF, 6
MOV R2, #&03FFFFFC                         ; => MVN R2, #&FF, 6
MOV R3, #&55555555                         ; Reports an error--cannot 
be constructed

Assembling the example

The above code is available in loadcon1.s in the examples directory. To assemble it first set the current directory to examples and then issue the command:

armasm loadcon1.s -o loadcon1.o -li

To confirm that armasm produced the correct code, the code area can be disassembled by looking at the output from:

decaof -c loadcon1.o

Explanation

The -li argument can be omitted if the tools have been configured appropriately.

decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.

LDR Rd, =numeric constant

armasm provides a mechanism which unlike MOV and MVN can construct any 32-bit numeric constant, but which may not result in a data processing operation to do it. This is the "LDR Rd, =" mechanism.

If the numeric constant can be constructed by using either MOV or MVN, then this will be the instruction used to load the constant. If this cannot be done, however, armasm will produce an LDR instruction to read the constant from a literal pool.

Literal pools

A literal pool is a portion of memory set aside for constants. By default a literal pool is placed right at the end of the program. However, for large programs, this literal pool may not be accessible throughout the program (due to the LDR offset being a 12 bit value), so further literal pools can be placed using the LTORG directive.

When the "LDR, Rd, =" mechanism needs to access a literal in a literal pool, armasm first checks previously encountered literal pools to see if the desired constant is already available and addressable. If it is then this literal is addressed, otherwise armasm will attempt to place the literal in the next available literal pool. If this literal pool is not addressable then an error will result, and an additional LTORG should be placed close to (but after) the failed "LDR Rd,=" instruction.

Although this may sound somewhat complicated, in practice, it is simple to use. Consider the following example, which demonstrates how literal pools and "LDR Rd,=" work. The instruction listed in the comment is the ARM instruction which gets produced by armasm.

  AREA Example, CODE, REL

  LDR R0, =42                  ;=> MOV R0, #42
  LDR R1, =&55555555           ;=> LDR R1, [PC, #offset to Literal Pool 1]
  LDR R2, =&FFFFFFFF           ; => MVN R2, #0

  LTORG                        ; Literal Pool 1 contains literal &55555555

  LDR R3, =&55555555           ; => LDR R3, [PC, #offset to Literal Pool 1]
; LDR R4, =&66666666           ; If this is uncommented it fails, Literal
                               ; Pool 2 is not accessible (out of reach)

LargeTable2 % 4200

  END                          ; Literal Pool 2 is empty

Assembling the example

The above code is available in loadcon2.s in the examples directory. To assemble it first set the current directory to examples and then issue the command:

armasm loadcon2.s -o loadcon2.o -li

To confirm that armasm produced the correct code, the code area can be disassembled by looking at the output from:

decaof -c loadcon2.o

Explanation

The -li argument can be omitted if the tools have been configured appropriately.

decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.

LDR Rd, =PC relative expression

As well as numeric constants, the "LDR Rd, =" mechanism can cope with PC relative expressions, such as labels.

Even if a PC relative ADD or SUB could be constructed, an LDR will be generated to load the PC relative expression. Thus if a PC relative ADD or SUB is desired then ADR should be used instead (see ADR and ADRL). If no suitable literal is already available, then the literal placed into the next literal pool will be the offset into the AREA, and an AREA relative relocation directive will be added to ensure that the constant is appropriate wherever the containing AREA gets located by the linker. See The handling of relocation directives for more information about relocation directives.

As an example consider the code below. The instruction listed in the comment is the ARM instruction which gets produced by armasm.

  AREA Example, CODE, REL

Start
  LDR R0, =StartLiteral                  ;=> LDR R0, [PC, #offset to Litpool 1
  LDR R1, =DataArea + 12                 ; => LDR R1, [PC, #offset to Litpool 1
  LDR R2, =DataArea + 6000               ; => LDR R2, [PC, #offset to Litpool 1

  LTORG                                  ; Literal Pool 1 holds three literals

  LDR R3, =DataArea + 6000               ; => LDR R2, [PC, #offset to Litpool 1
                                         ; (sharing with previous literal)
; LDR R4, =DataArea + 6004               ; If uncommented will produce an error
                                         ; as Litpool 2 is out of range
   
DataArea % 8000

  END                                    ; Literal Pool 2 is out of range of
                                         ; the LDR instructions above

Assembling the example

The above code is available in loadcon3.s in the examples directory. To assemble it first set the current directory to examples and then issue the command:

armasm loadcon3.s -o loadcon3.o -li

To confirm that armasm produced the correct code, the code area can be disassembled by looking at the output from:

decaof -c loadcon3.o

Explanation

The -li argument can be omitted if the tools have been configured appropriately.

decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.

ADR and ADRL

Sometimes it is important for efficiency purposes that loading an address does not perform a memory access. The assembler provides two pseudo instructions which make it easier to do this.

Whereas MOV and MVN only accept numeric constants, ADR and ADRL accept numeric constants, PC relative expressions (labels within the same area) and register relative expressions.

ADR will attempt to produce a single data processing instruction to load an address into a register. This instruction will be one of MOV, MVN, ADD or SUB, in the same way as the "LDR Rd, =" mechanism produces instructions. If the desired address cannot be constructed in a single instruction an error will be produced.

ADRL will attempt to produce either two data processing instructions to load an address into a register. Even if it is possible to produce a single data processing instruction to load the address into the register then a second, redundant instruction will be produced (this is a consequence of the strict two-pass nature of armasm) . In cases where it is not possible to construct the address using two data processing instructions ADRL will produce an error - the LDR, = mechanism is probably the best option in this case.

As an example consider the code below. The instructions listed in the comments are the ARM instruction which are produced by armasm.

  AREA Example, CODE, REL

Start
  ADR  R0, &8000                      ; => MOV R0, #&8000
; ADR  R1, &8001                      ; This would fail as it cannot be
                                      ; constructed by a MOV or MVN
  ADR  R2, Start                      ; => SUB R2, PC, #offset to Start
  ADR  R3, DataArea                   ; => ADD R3, PC, #offset to DataArea
; ADR  R4, DataArea+4300              ; This would fail as the offset cannot
                                      ; be expressed by operand2 of an ADD
  ADRL R5, DataArea+4300              ; => ADD R5, PC, #offset1
                                      ;    ADD R5, R5, #offset2
  ADRL R6, &8001                      ; => MOV R6, #1
                                      ;    ADD R6, R6, #&8000
; ADRL R7, &55555555                  ; This would fail--the constant can't
                                      ; be constructed by 2 data processing
                                      ; instructions
DataArea % 8000

  END

Assembling the example

The above code is available in loadcon4.s in the examples directory. To assemble it first set the current directory to examples and then issue the command:

armasm loadcon4.s -o loadcon4.o -li

To confirm that armasm produced the correct code, the code area can be disassembled by looking at the output from:

decaof -c loadcon4.o

Explanation

The -li argument can be omitted if the tools have been configured appropriately.

decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.

Related topics

For more information on the capabilities of the barrel shifter see Using the Barrel Shifter.