This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Why is PC-relative addressing deprecated for STR and VSTR in ARMv7-M4?

When the operand field of a LDR instruction uses a label to reference a memory location, it is implemented as a “PC-relative” reference, as in:

LDR R0,x // ‘PC-Relative’ addressing

This assumes that the data is stored in what ARM calls a “literal pool” located a short distance from the instruction. The assembler calculates this distance and embeds it as a constant within the representation of the instruction. When the instruction executes, the constant is added to the current value of the program counter (PC) to determine the address of the operand.

However, the use of PC-relative addressing with the STR instruction was deprecated in the Thumb-2 instruction set introduced as part of the ARMv7-M architecture.

STR R0,x // Rejected by the assembler as ‘deprecated’

Instead, STR references to labelled data require a two-instruction sequence:

ADR R1,x // R1 = address of x
STR R0,[R1] // R0 stored at address provided by R1

One might reasonably assume (although incorrectly) that PC-relative addressing is a special case of “Immediate Offset” addressing in which the address is the sum of a register (the PC in this case) and a constant, as in:

LDR R0,[PC,constant] // Immediate Offset addressing

For example, the T3 encoding of a “Load Immediate” instruction specifies the register (Rn) and constant (imm12) in bitfields of 4 and 12 bits each, so it would seem that PC-relative addressing would simply insert the register code of the PC (R15 = 1111) into the Rn bitfield of the instruction:

LDR Rt,[Rn,imm12] // ‘Immediate mode’ addressing

However, the constant (imm12) is unsigned, so this would limit PC-relative references to only “forward” references. Instead, setting Rn to 1111 is a special case that causes the execution of the instruction to interpret bit 7 (the ‘U’ bit) of the encoding differently. When U=1, the address is computed as PC + imm12, but when U=0, it is computed as PC – imm12. This instruction, known as “LDR Literal”, can then make both forward and backward references to literal pool data.

Unfortunately, although there is an “STR Immediate”, specifying Rn = 1111 causes the instruction to be undefined. I.e., there is no “STR Literal” instruction. Why?

Parents
  • In general, STR  Rt,[PC, #offset] (or VSTR) is likely to cause part of the software image to be self modified. In general, self modifying software is not recommended as it is hard to debug and when things go wrong, the system is unlikely to be able recovered. There is also implications in industrial and automotive applications - how can you verify the self modifying code as functionally safe? (e.g. static code analysis cannot be used).

    For microcontroller world, since in most devices the program will be executed from embedded flash (which is read-only), it is impossible to handle self modifying code anyway. So it doesn't make sense to keep these instructions.

Reply
  • In general, STR  Rt,[PC, #offset] (or VSTR) is likely to cause part of the software image to be self modified. In general, self modifying software is not recommended as it is hard to debug and when things go wrong, the system is unlikely to be able recovered. There is also implications in industrial and automotive applications - how can you verify the self modifying code as functionally safe? (e.g. static code analysis cannot be used).

    For microcontroller world, since in most devices the program will be executed from embedded flash (which is read-only), it is impossible to handle self modifying code anyway. So it doesn't make sense to keep these instructions.

Children
  • Hi Joseph,

    Thank you for jogging my memory! I guess I was so used to the Intel architecture where memory store instructions can reference variables using identifiers as in:

    MOV BYTE PTR [x], 5

    that I just assumed that I should be able to do the same in ARM. Of course, that Intel instruction assumes that by default variable x resides in the data segment, and not the code segment. Intel writes to a labeled location in the code segment would require an explicit code segment override prefix, which would make any attempt at self-modifying code a bit more obvious:

    MOV BYTE PTR CS:[x],5

    Your comment also made me realize that even if an ARM PC-relative STR was available, it could only reference a memory location that is +/-4095 bytes from the STR instruction, and that this would usually mean that the destination address is in flash memory and not in RAM. Clearly an executing program cannot physically write to flash memory without going through a much more complicated procedure to first enable flash updates.

    Best,

    Dan

  • Joseph,

    A bit of follow-up: Please correct me if I'm wrong, but I believe the ADR instruction can only reference a label that is within 4095 bytes of the ADR instruction. If that's true, then it would seem to me that an ADR/STR sequence is still likely to be restricted to storing into flash. Am I missing something?

    Dan

  • Yes, ADR also has 4KB PC relative address range.

    One thing that we use often is SP relative addressing: LDR/STR  Rd,[SP, #offset]

    For example, in the beginning of a function we decrement SP by N bytes to allocate local variable space, then the local variable location can be addressed using SP relative addressing.. At the end of the function we increment SP by N to free that allocated space.

    regards,

    Joseph

  • Then for static variables, I suppose the only way to do a STR requires something like:

            .data

    var:  .word    0

            .code

            LDR     R1,adrOfvar

            STR     R0,[R1]

            ...

    adrOfvar: .word  var

  • Hi ,

    With gas/gcc you should be able to do:

            .bss
    
    var:    .word   0
    
            .text
    
            ldr     r1, =var
            str     r0, [r1]

    The "=" will create the litpool entry for you.

  • Thanks! I actually realized that later, and certainly it's a lot easier to let the assembler handle the details! :-)

  • Actually, if you use a lot of variables, I'd recommend to use a dedicated base register and work with offsets.