Problem in inline assembly going around LLVM internally used register

I was trying some code to execute on RISC OS and I was using inline assembly to directly call their SWIs. This was fine until I faced a SWI that used the r6 register, OS_DynamicArea 0. In principle the code would be the following.

unsafe { asm!(
	"mov r5,r2",
	"mov r0,#0",
	"mov r1,#-1",
	"mov r3,#-1",
	"mov r4,#0",
	"mov r6,#0",
	"mov r7,#-1",
	"mov r8,#0",
	"swi 0x66",//OS_DynamicArea
	in("r2") size,
	out("r3") address,
	out("r1") area_number,
	// clobbered registers
	out("r0") _,
	out("r4") _,
	out("r5") _,
	out("r6") _,
	out("r7") _,
	out("r8") _,
	out("r9") _,
); }

However, the compiler will complain .

invalid register r6: r6 is used internally by LLVM and cannot be used as an operand for inline asm

As the SWI uses that register I must go around the restriction in some way. I have not seen any established way to proceed, so I made my own. I tried to request any register to the compiler choice to use for saving r6.

unsafe { asm!(
	"mov {save6},r6",
	"mov r5,r2",
	"mov r0,#0",
	"mov r1,#-1",
	"mov r3,#-1",
	"mov r4,#0",
	"mov r6,#0",
	"mov r7,#-1",
	"mov r8,#0",
	"swi 0x66",//OS_DynamicArea
	"mov r6,{save6}",
	in("r2") size,
	out("r3") address,
	out("r1") area_number,
	// clobbered registers
	out("r0") _,
	out("r4") _,
	out("r5") _,
	save6 = out(reg) _,
	out("r7") _,
	out("r8") _,
	out("r9") _,
); }

Thus, I expected the compiler to choose some save6 register, marked as out and hence available to use. Then save r6 into it at the start and recover r6 at the end to preserve whatever LLVM is doing with it. This seemed to work for some time, but exploded later. I looked into it and I saw that save6 was being chosen as r6 itself. That is, out(reg) was selecting r6 despite it being supposedly not eligible.

This seems to me to be a bug in the compiler. Can someone confirm this?

Then I decided to just choose myself the new register, and it seems to work ok.

unsafe { asm!(
	"mov r10,r6",
	"mov r5,r2",
	"mov r0,#0",
	"mov r1,#-1",
	"mov r3,#-1",
	"mov r4,#0",
	"mov r6,#0",
	"mov r7,#-1",
	"mov r8,#0",
	"swi 0x66",//OS_DynamicArea
	"mov r6,r10",
	in("r2") size,
	out("r3") address,
	out("r1") area_number,
	// clobbered registers
	out("r0") _,
	out("r4") _,
	out("r5") _,
	out("r10") _,
	out("r7") _,
	out("r8") _,
	out("r9") _,
); }

But perhaps I am still doing something cursed.

2 Likes

Are you sure? r6 may, sometimes, indeed, be used by LLVM, but in such a case it shouldn't give it for assembler code to use!

Can you double check that it's specifically that r6 clobbering that broke your code?

According to the documentation everything should work even with r6 clobbering.

Small reproducer would be nice to submit against rustc if it's indeed uses r6 for accessing stack and, simultaneously, pass it as an in/out register into asm.

Actually, the program normally works fine in both ways. It is when running it through some other buggy tool when the problems manifest. Perhaps it is all due to some of its bugs, but when I saw those mov r6,r6 in the disassembly I thought something must be wrong here. I know that there was content in that r6, and that that when the bug happened it was a panic a few lines after the involved SWI.

I have not tried yet to make a smallest reproducible instance, it is directly the code of my toy. It is the first time I mess with inline assembly in rust and I am yet a bit unsure on some specifics, so I thought it was better to ask.

Can you point me to this? I saw already the post two below the one you cite, which says "the programmer is responsible for saving and restoring r6". It follows that if r6 is clobbered instead of preserved then something wrong would happen. Again, I do not really know what.

Yes, that's true, but LLVM, itself, allowed you to clobber r6 by making it out register.

It shouldn't do that if/when it needs it for something else.

1 Like

Thank you, so both versions should work then. And otherwise, I should report as bug.

I remember that it helped when I changed manually the value of the register r6 to which it had before, so it would seem that something is wrong. But that was using the buggy tool and it could have been some other problem. I will try to make the bug happen normally. When I get some time.

It's similar to use of frame pointer in clang. Normally, in today's world frame pointer is not needed and not used. But if your C program calls alloca or uses VLA then compiler would use it to track dynamic size of frame.

Someone may request compiler to generate stack frames unconditionally, too.

That's why your assembler code shouldn't use it as input-output.

But if compiler have decided to use frame pointer in a particular function then it shouldn't simultaneously use it for inline assembler operands! But it's perfectly Ok to use it in most other functions.

1 Like

I just want to say that I am now confident the problem was an unrelated issue of the buggy tool (an uninitialized bss). The code did not use variable sizes on the stack and r6 was being used as any other variable.

@khimru Thank you for your help on this, much appreciated.