Search code examples
cpointersinline-assemblycpu-registersthumb

Loading a the address of a pointer into a register inline thumb assembly


I'm trying to read in the address from my c-pointer to a register using inline thumb assembly.

here's a reproducible:

static uint32_t volatile * volatile CurrentTaskStackPtr;
CurrentTaskStackPtr = (uint32_t *) 0x20000001;
__asm volatile("LDR R8, =%0" : : "i"(CurrentTaskStackPtr) );

Also, I'm trying to avoid changing the state of any 'context' registers, and that's why I use an immediate read.

I keep getting this error:

warning: 'asm' operand 0 probably does not match constraints
  187 |         __asm volatile("LDR R8, =%0" : : "i"(CurrentTaskStackPtr) );

I have tried using different constraint characters, but I'm not familiar enough with assembly to make any progress, any help would be appreciated.

Thanks.


Solution

  • "i"(CurrentTaskStackPtr) is asking for the value of a C variable as an immediate. That can only work if the C variable is a compile-time constant, like uint32_t *const CurrentTaskStackPtr = .... But your variable is volatile (separately from pointing to volatile), so even with optimization enabled, you've forbidden the compiler from doing constant-propagation through the assignment to make "i"(0x20000001).

    If you want the address, take the address, "i"(&CurrentTaskStackPtr).


    Or better, ask the compiler to have the address in R8 for you, instead of putting an ldr pseudo-instruction into your asm template, unless you need to control how it materializes the address or something.

        register void *tmp_r8 asm("r8");   // forces "r" constraints to pick R8.
        tmp_r8 = &CurrentTaskStackPtr;
        asm("..."
           : // outputs
           : "r"(tmp_r8)
           : "memory"  // presumably you're going to deref that pointer
        );
    

    See also https://stackoverflow.com/tags/inline-assembly/info and ARM inline asm: exit system call with value read from memory for an example of how this compiles.

    The GCC manual (https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html) is explicit that the only guaranteed behaviour of an register asm local variable is picking the specified register for inline asm statements; it's not guaranteed to use that register at any other time.

    See also How can I indicate that the memory *pointed* to by an inline ASM argument may be used? for more about why the "memory" clobber is necessary. Probably best to use that instead of dummy input/output operands; you don't want the compiler trying to optimize around a context-switch anyway.


    Trying to do context switching

    You might have a better time writing a context-switch function as __attribute__((naked)), so you don't have to worry about when the compiler accesses local vars relative to the stack pointer. Changing the stack pointer inside an asm statement is not officially supported. (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#index-asm-clobbers - the clobber list shouldn't contain the stack pointer.)

    Except in a naked function, where you're writing the entire function's asm in a Basic Asm statement, and it can't inline into any callers since it's like __attribute__((noinline,noipa)), or like writing it in a separate .s file and just defining a prototype.