I have the below inline assembly code:
int get_year(int a, int *b, char * c)
{
int ret, t1, t2;
asm (
\"addl %3, %[a] \\n\\
First of all, if you're trying to get your feet wet learning asm, GNU C inline asm is one of the hardest ways to use asm. Not only do you have to write correct asm, you have to spend a lot of time using esoteric syntax to inform the compiler of exactly what your code needs for input and output operands, or you will have a bad time. Writing whole functions in ASM is much easier. They can't be inlined, but it's a learning exercise anyway. The normal function ABI is much simpler than the boundary between C and inline ASM with constraints. See the x86 wiki...
Besides that compile error, you have a bug: you clobber %[a]
, even though you told gcc it's an input-only operand.
I assume this still a "work in progress", since you could get the same result with better code. (e.g. using %edx
as a scratch reg is totally unnecessary.) Of course, in the general case where this is inlined into code where a
might be a compile-time constant, or known to be related to something else, you'd get better code from just doing it in C (unless you spent a lot of time making inline-asm variants for various cases.)
int get_year(int a, int *b, char * c)
{
int ret, t1, t2;
asm (
"addl %[bval], %[a] \n\t"
"movb $58, 4 + %[cval]\n\t" // c is an "offsetable" memory operand
: [t1] "=&r" (t1), [cval] "=o" (*c)
: [a] "0" (a), [bval] "erm" (*b)
: // no longer clobbers memory, because we use an output memory operand.
);
ret = t1; // silly redundancy here, could have just used a as an input/output operand and returned it, since you apparently want the value
return ret;
}
This now compiles and assembles (using godbolt's "binary" option to actually assemble). The 4 + (%rdx)
produces a warning, but does assemble to 4(%rdx)
. IDK how to write the offset in a way that doesn't error if there's already an offset. (e.g. if the operand is *(c+4)
, so the generated asm is 4 + 4(%rdx)
, it wouldn't work to leave out the +
.)
This is still using the matching-output-operand trick, but I changed to using memory or general constraints to allow compiler-time constants to end up doing a addl $constant, %edi
.
This allows the compiler as much flexibility as possible when inlining. e.g. if a caller ran get_year(10, &arr[10], &some_struct.char_member)
, it could use whatever addressing mode it wanted for the load and store, instead of having to generate c
in a single register. So the inlined output could end up being movb $58, 4+16(%rbp, %rbx)
for example, instead of forcing it to use 4(%reg)
.
I can reproduce the problem if I compile your code with clang only when generating 64-bit code. When targeting 32-bit code there's no error. As Michael Petch said, this suggests the problem is the different sizes of the two operands.
It's not entirely clear what the best fix would be, as your asm statement doesn't make much sense. It's the equivalent of:
int get_year(int a, int *b, char *c) {
a += *b;
c[4] = 58;
return a;
}
There's no advantage to using an assembly statement to do what can be done more clearly and more efficiently using the C code above. So the best solution would be completely replace your code with the equivalent C code.
If you're just playing around with inline assembly, then equivalent inline assembly would be:
int get_year2(int a, int *b, char * c)
{
asm("addl %[b], %[a]"
: [a] "+r" (a)
: [b] "m" (*b)
: "cc");
asm("movb $58, %[c4]"
: [c4] "=rm" (c[4]));
return a;
}
I've used two asm statements because the two parts are unrelated. Keeping them separated provides for more opportunities for optimization. For example if you call this function but don't use the return value, the compiler can eliminate the first asm statement because its result isn't used and it has no side effects.
Instead of using a matching constraints, the "1"
constraint that was giving you problems, I've used the "+" constraint modifier to mark the operand as both an input and an output. I find this works better. The constraint for the [b]
operand should really be "rm"
but unfortunately clang doesn't handle rm constraints well.
You probably noticed that I've used only two assembly statements where your example used four. The MOVL instruction isn't necessary, the compiler can handle moving the result in to the return value register, if necessary. Your last two assembly statements can be collapsed into one single statement that moves the constant directly into memory without clobbering a register. Speaking of which, your asm statement clobbers EFLAGS, the condition codes, so "cc"
should be listed a clobbered, but as Peter Cordes notes it's not necessary with x86 targets but the compiler assumes they are anyways.