Re: Specifying different registers in inline asm

"Richard Earnshaw \(foss\) via Gcc-help" <[email protected]>
Newsgroups gmane.comp.gcc.help
Message-ID <[email protected]>
On 19/11/2025 11:48, jh--- via Gcc-help wrote:
> Le 2025-11-19 11:18, Richard Earnshaw (foss) a écrit :
>> On 19/11/2025 08:03, jh--- via Gcc-help wrote:
>>> Le 2025-11-18 18:10, Segher Boessenkool a écrit :
>>>> Hi!
>>>>
>>>> On Tue, Nov 18, 2025 at 06:01:33PM +0100, [email protected] wrote:
>>>>> Le 2025-11-18 17:43, Segher Boessenkool a écrit :
>>>>> > On Tue, Nov 18, 2025 at 11:22:40AM +0100, jh--- via Gcc-help wrote:
>>>>> > > I am sorry if the answer is trivial, but I couldn't find it.
>>>>> > > I need to access a specific instruction on ARM, so I used an inline
>>>>> > > asm
>>>>> > > block to do it.
>>>>> > > My problem is that the instruction (LDRBT) reads a byte in a
>>>>> > > register from
>>>>> > > an address, but the address is written back to its register as well,
>>>>> > > so the
>>>>> >
>>>>> > If an operand is both read and written, you should inform the compiler
>>>>> > of that fact.  Typically by marking it as "+" (i.e. read/write) output
>>>>> > constraint.
>>>>> I tried marking the address parameter with a "+", but since it's not an
>>>>> lvalue (I call the macro with &buf[cnt]), it did not work.
>>>>
>>>> The first copy it into a variable that *is* writable?  This is required
>>>> for correctness, since your asm *does* modify the reg!
>>> I'm not sure about this situation, for me there are two sides on this matter: a practical one and a theoretical one.
>>> On the practical side, the instruction does a write back to the register, but with the same value (because omission of the offset is equal to 0 in this instance), so the register does not change value.
>>> On a theoretical side, I would like to tell the compiler that the register is clobbered, that I don't care about the value anymore, but that if the compiler needs the value, it cannot rely on the fact that the register still contains it. I think it is not possible to specify clobbers for input values, but it makes sense to be able to use a non-lvalue here, so I don't want to specify it as an output. Is this doable?
>>>
>>>>> > > destination and the address register should be different (as helpfully
>>>>> > > mentioned by the assembler: "destination register same as write-back
>>>>> > > base").
>>>>> > > The code generator produces "LDRBT R3, [R3]" which is not correct.
>>>>> > > I would like to specify that I want two distinct registers for %0
>>>>> > > and %1 (my
>>>>> > > code is LDRBT %0, [%1]), but I did not find how.
>>>>> >
>>>>> > If you want literally what you ask for (but read above), what you want
>>>>> > is an "earlyclobber".  Written as "&", see the manual.
>>>>> >
>>>>> Thank you very much, it is so obviously described, once you know where to
>>>>> look.
>>>>
>>>> As always :-)
>>>>
>>>> One thing you can try is "read everything".  I've been trying to do that
>>>> forever now, and am still not done, but making some progress I hope ;-)
>>> Nah, the real method is to "read everything an infinite number of times", because on each pass you understand something that you have read before, but which did not click into place. But as everyone knows eternity is very long, especially towards the end.
>>>
>>>>> > > Could you please tell me what the situation is here (I could make a
>>>>> > > work-around by moving the result to another register and using the
>>>>> > > clobber
>>>>> > > list, but that's inefficient on several scales)?
>>>>> > > Also, more generally, is this not an issue that could be considered
>>>>> > > being
>>>>> > > addressed, maybe by adding to the asm constraints (if it is not yet
>>>>> > > the
>>>>> > > case) or by adding it to the documentation (as an example)?
>>>>> >
>>>>> > It is a pretty common problem, and that is why it has been handled since
>>>>> > decades already :-)
>>>>> We---ell you know, sometimes (most of the times) you think you have the most
>>>>> important problem in the world and it's not the case.
>>>>> Also it IS documented, and I don't really know how it could be any more
>>>>> obvious (except maybe as part of a 'cookbook'. Or using AI as RAG, but who
>>>>> wants to do that?).
>>>>>
>>>>> > Segher
>>>>> PS.: someone beat you to the answer, and I am grateful to both of you, but
>>>>> sorry I wasted the time of two person, what did I do wrong that you did not
>>>>> know that someone already answered?
>>>>
>>>> I read emails in the order they come in.  I don't always read hundreds
>>>> or more of messages ahead before I reply to something, that just doesn't
>>>> scale.
>>>>
>>>> Did Xi answer before me?  I think our answers just crossed even, a very
>>>> common thing :-)
>>> Anyway thanks a lot.
>>> JH
>>
>> Well you could write:
>>
>>   int dummy;
>>   int result;
>>   asm ("ldrbt\t%0, [%1]" : "=r"(result), "=r"(dummy) : "1"(addr));
>>
>> which describes the behaviour more precisely.  The "1" tells the
>> compiler that addr and dummy must use the same register, but addr will
>> remain unchanged (so must be copied if it will be used again).
>>
>> Or, if you don't mind addr being direclty clobbered, you can write:
>>
>>   int result;
>>   asm ("ldrbt\t%0, [%1]" : "=r"(result), "+r"(addr));
>>
>> telling the compiler that addr is modified as an inout value.
>>
>> R.
> Hi,
> 
> Thank you for your answer.
> Here is my code:
> 
> #define byte_from_user(addr)     ({uint8_t val; __asm__ __volatile__("LDRBT    %0, [%1]\n" : "=&r"(val) : "r" (addr), "m"(*addr)); val;})
> 
> void dump(const uint8_t * src, size_t count;
>     for(size_t cntsrc=0;cntsrc<count;cntsrc++){
>         uint8_t data = byte_from_user(&src[cntsrc]);
>         printf("%02X ", data);
>     }
>     printf("\n");
> }
> 
> So the only way to be theoretically correct is to copy addr to a dummy variable and say it will come out of the asm block? Like this:
> 
> #define byte_from_user(addr)     ({uint8_t val; uintptr_t clob_addr = addr; __asm__ __volatile__("LDRBT    %0, [%1]\n" : "=&r"(val), "+r"(clob_addr) : "m"(*clob_addr)); val;})
> 

With the '+r' variant you don't need to mention clob_addr twice.  You also don't need '&' on the first value now because the compiler will never assign two outputs to the same register.

So I think you're best solution would be:

 
static inline uint8_t
byte_from_user(uint8_t *addr)
{
  uint8_t val;
  void *unused;
  __asm__ __volatile__ ("LDRBT    %0, [%1]\n"
			: "=r"(val), "=r" (unused)
			: "1" (addr)
			: "memory");
  return val;
}

void dump(const uint8_t * src, size_t count;
    for(size_t cntsrc=0;cntsrc<count;cntsrc++){
        uint8_t data = byte_from_user(&src[cntsrc]);
        printf("%02X ", data);
    }
    printf("\n");
}

You probably won't need the clobber of 'memory' in addition to the 'volatile' qualifier, but you certainly need at least one.

> A bit seemingly unnecessarily verbose, but I can live with it if the compiler can deduce it should just produce LDRBT Rx, [Ry] with x!=y and know that Ry is clobbered. (which is actually not the case because it is rewritten with the same value, but this has been described above).
> 
> Cheers,
> JH

R.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.