You haven't said which processor you're generating code for... I assume it's a RISC machine because your subtract instruction has 3 operands, but you haven't said which one. I'm going to show you what this looks for x86 because I know the answer is correct.
I also assume by your use of asm volatile
that you're using a compiler that follows gcc's standard.
Anyway, let's say you had a struct like this:
struct mystruct {
unsigned char a;
const char *b;
int c;
};
int a_or_c(mystruct *str) {
int a_val = str->a;
return a_val & 1 ? str->c : (a_val >> 1);
}
The compiler doesn't generate particularly good code for that - it's seven instructions and we can do much better because we know that one instruction can do both the test for "& 1" and the shift right.
To specify a register, you use r
for "register", as I'm sure you know. To specify an in/out register you use +r
. To specify a constant like a struct offset, you use i
for "immediate". Then the assembler syntax would be, for example:
int a_or_c_asm(mystruct *str) {
int a_val = str->a;
asm("shr $1,%0\n\t"
"cmovcl %c2(%1), %0"
: "+r"(a_val)
: "r"(str), "i"(offsetof(mystruct, c))
: "memory" // tell the compiler that our code reads from memory
);
return a_val;
}
The trick here is you have to use %c2
rather than merely %2
to get the inline assembler to output a 2
rather than $2
, because the x86 assembler uses a different syntax for offsets in addressing modes, than it does for immediate operands. A subtract instruction in x86 would look like this, for example:
asm("subq %0, %%rsp"
"... other instructions ..."
: // no output operands
: "i"(offsetof(mystruct, c)));
// expands to subq $16, %rsp for x86-64
By your comment I assume you need ARM32 syntax. For that, your subtract instruction would look like this:
asm("sub sp, %0"
"... other instructions ..."
: // no output operands
: "i"(offsetof(mystruct, c)));
// expands to sub sp, #8 for ARM
(Obligatory Godbolt: compiles and assembles correctly for ARM.)
(Obligatory Godbolt: compiles and assembles correctly for x86.)
Please note that gcc assumes the stack pointer is the same at the end of any assembly block as it is at the start; the rest of your assembly block will have to include an instruction to restore sp to its original value.
For ARM32, my example would look like this - note the different syntax for using a register-plus-offset addressing mode in ARM32:
int a_or_c_asm(mystruct *str) {
int a_val = str->a;
asm("lsrs %0, #1\t\n" // shift and set flags
"ldrcs %0, [%1, %2]" // load (predicated on Carry Set)
: "+r"(a_val)
: "r"(str), "i"(offsetof(mystruct, c))
: "memory" // we access memory that isn't a declared input.
);
return a_val;
}
Of course, this is all contrived to show how to pass a known constant value to gcc's inline asm syntax. A more common alternative would be to have a "m"(str->c)
input operand that you use for ldrcs %0, %1
., so that you don't have to use the offsetof macro.
Also, rather than using "memory"
, you could pass a dummy input operand to tell the compiler that the c
field is an input, but you still actually form the addressing mode yourself; for more on this see How can I indicate that the memory *pointed* to by an inline ASM argument may be used?