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
To confirm that armasm produced the correct code, the code area can
be disassembled by looking at the output from:
armasm loadcon1.s -o loadcon1.o -li
decaof -c loadcon1.o
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.
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
To confirm that armasm produced the correct code, the code area can
be disassembled by looking at the output from:
armasm loadcon2.s -o loadcon2.o -li
decaof -c loadcon2.o
decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.
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
To confirm that armasm produced the correct code, the code area can
be disassembled by looking at the output from:
armasm loadcon3.s -o loadcon3.o -li
decaof -c loadcon3.o
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
To confirm that armasm produced the correct code, the code area can
be disassembled by looking at the output from:
armasm loadcon4.s -o loadcon4.o -li
decaof -c loadcon4.o
decaof is the ARM Object Format decoder. The -c option requests that decaof dissassemble the code area.