6

I am trying to understand some things about inline assembler in Linux. I am using following function:

void test_func(Word32 *var){
   asm( " addl %0, %%eax" : : "m"(var) );
   return;
}

It generates following assembler code:

.globl test_func
.type   test_func, @function
test_func:
        pushl %ebp
        movl %esp, %ebp
#APP
# 336 "opers.c" 1
        addl 8(%ebp), %eax
# 0 "" 2
#NO_APP
        popl %ebp
        ret
        .size   test_func, .-test_func

It sums var mem address to eax register value instead var value.

Is there any way to tell addl instruction to use var value instead var mem address without copying var mem address to a register?

Regards

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
LooPer
  • 1,459
  • 2
  • 15
  • 24
  • Your inline asm doesn't define what is supposed to be in EAX, so gcc could put anything there... what are you actually trying to do? – servn Aug 22 '11 at 01:54

5 Answers5

4

yes, because you give him var which is address. give him *var.

like:

void test_func(Word32 *var){
   asm( " addl %0, %%eax" : : "m"(*var) );
   return;
}

i don't remember exactly, but you should replace "m" with "r" ?

memory operand doesn;t mean that it will take value from that address. it's just a pointer

fazo
  • 1,807
  • 12
  • 15
4

It sums var mem address to eax register value instead var value.

Yes, the syntax of gcc inline assembly is pretty arcane. Paraphrasing from the relevant section in the GCC Inline Assembly HOWTO "m" roughly gives you the memory location of the C-variable.

It's what you'd use when you just want an address you can write to or read from. Notice I said the location of the C variable, so %0 is set to the address of Word32 *var - you have a pointer to a pointer. A C translation of the inline assembly block could look like EAX += *(&var) because you can say that the "m" constraint implicitly takes the address of the C variable and gives you an address expression, that you then add to %eax.

Is there any way to tell addl instruction to use var value instead var mem address without copying var mem address to a register?

That depends on what you mean. You need to get var from the stack, so someone has to dereference memory (see @Bo Perssons answer), but you don't have to do it in inline assembly

The constraint needs to be "m"(*var) (as @fazo suggested). That will give you the memory location of the value that var is pointing to, rather than a memory location pointing to it.

The generated code is now:

test_func:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
#APP
# 2 "test.c" 1
    addl    (%eax), %eax
# 0 "" 2
#NO_APP
    popl    %ebp
    ret

Which is a little suspect, but that's understandable as you forgot to tell GCC that you clobbered (modified without having in the input/output list) %eax. Fixing that asm("addl %0, %%eax" : : "m"(*var) : "%eax" ) generates:

    movl    8(%ebp), %edx
    addl    (%edx), %eax

Which isn't any better or more correct in this case, but it is always a good practice to remember. See the section on the clobber list and pay special attention to the "memory" clobber for advanced usage of inline assembly.

Even though you don't want to (explicitly) load the memory address into a register I'll briefly cover it. Changing the constraint from "m" to "r" almost seems to work, the relevant sections gets changed to (if we include %eax in the clobber list):

    movl    8(%ebp), %edx
    addl    %edx, %eax

Which is almost correct, we have loaded the pointer value var into a register, but now we have to specify ourselves that we're loading from memory. Changing the code to match the constraint (usually undesirable, I'm only showing it for completeness):

asm("addl (%0), %%eax" : : "r"(var) : "%eax" );

Gives:

movl    8(%ebp), %edx
addl    (%edx), %eax

The same as with "m".

user786653
  • 29,780
  • 4
  • 43
  • 53
  • *you have a pointer to a pointer* - not exactly. You have an *addressing mode* that refers to the pointer object's memory. If it's not a simple bare-register addressing mode then you only ever "have" that pointer inside an AGU in the CPU, not as a value you can do anything with. Also, `"m"` is a read-only operand. If you want to read and write, you need `"+m"`, so that early paragraph that mentions read and write for `"m"` isn't quite accurate, unless you're assuming adding `+` or `=` modifiers as necessary. – Peter Cordes Sep 10 '21 at 13:17
  • I added [my own answer](https://stackoverflow.com/questions/7139305/gnu-c-inline-asm-m-constraint-with-a-pointer-address-vs-pointed-to-value/69139133#69139133). I don't think your description in terms of `*(&var)` is very clear or helpful, because in C terms there's no deref, and no address being taken. You're just using `var`, of type `Word32*`, as the input object, exactly like `eax += var` would do in pure C. – Peter Cordes Sep 11 '21 at 00:45
  • Also, your `asm("addl (%0), %%eax" : : "r"(var) : "%eax" );` isn't safe; you don't tell the compiler that the pointed-to memory is an input or use a `"memory"` clobber, so that could break after inlining. [GNU C inline asm "m" constraint with a pointer: address vs. pointed-to value?](https://stackoverflow.com/q/7139305) – Peter Cordes Sep 11 '21 at 00:46
1

An "m" constraint doesn't implicitly dereference anything. It's just like an "r" constraint, except it expands to an addressing mode for a memory location holding the value of the expression, instead of a register. (In C, every object has an address, although often that can be optimized away.)

The C object that's an input (or output for "=m") for the asm is the lvalue or rvalue you specify, e.g. "m"(var) takes the value of var, not *var. So you'd adding the pointer. (And telling the compiler that you want that input pointer value to be in memory, not a register.)

Perhaps it's confusing you that you have a pointer but you called it var, not ptr or something? A C pointer is an object whose value is an address, and can itself be stored in memory. If you were using C++, Word32 &var would make the dereference implicit whenever you write var.


In C terms, you're doing eax += ptr, but you want eax += *ptr, so you should write

void test_func(Word32 *ptr){
    asm( "add %[input], %%eax"
      :  // no inputs.  Probably you should use "+a"(add_to_this) if you want  the add result, and remove the EAX clobber.
      : [input] "m"(*ptr)   // the pointed-to Word32 in memory
      : "eax"       // the instruction modifies EAX; tell the compiler about it
    );
}

Compiling (Godbolt compiler explorer) results in:

# gcc -O3 -m32
test_func:
        movl    4(%esp), %edx      # compiler-generated load of the function arg
        add (%edx), %eax          # from asm template, (%edx) filled in as %[input] for *ptr
        ret

Or if you'd compiled with -mregparm=3, or a 64-bit build, the arg would already be in a register. e.g. 64-bit GCC emits add (%rdi), %eax ; ret.

If you'd written return *ptr in C for a function returning Word32, with no inline asm, the asm would be similar, loading the pointer arg from the stack and then mov (%edx), %eax to load the return value. See the Godbolt link for that.

If inline asm isn't doing what you expect, look at the compiler generated asm to see how it filled in your template. That sometimes helps you figure out what the compiler thought you meant. (But only if you understand the basic design principles.)


If you write "m"(ptr), it compiles as follows:

void add_pointer(Word32 *ptr)
{
    asm( "add %[input], %%eax"   :  : [input] "m"(ptr)   : "eax"  );
}
add_pointer:
        add 4(%esp), %eax       # ptr
        ret     

Very similar to if you wrote Word32 *bar(Word32 *ptr){ return ptr; }


Note that if you wanted to increment the memory location, you'd use a "+m"(*ptr) constraint to tell the compiler that the pointed-to memory is both an input and output. Or if you write-only to the memory, "=m"(*ptr) so it can potentially optimize away earlier dead stores to this memory location.


See also How can I indicate that the memory *pointed* to by an inline ASM argument may be used? to handle cases where you use an "r"(ptr) input and dereference the pointer manually inside the asm, accessing memory that you didn't tell the compiler about as being an input or output operand.

Generally avoid doing "r"(ptr) and then manually doing add (%0), %%eax. It needs extra constraints to make it safe, and it forces the compiler to materialize the exact address in a register, instead of using an addressing mode to reach it relative to some other register. e.g. 4(%ecx) if after inlining it sees that you're actually passing a pointer into an array or to a struct member.

Of course, generally avoid inline asm entirely unless you can't get the compiler to emit good enough asm without it. https://gcc.gnu.org/wiki/DontUseInlineAsm. If you do decide to use it, see https://stackoverflow.com/tags/inline-assembly/info for guides to avoid common mistakes.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
1

No, there is no addressing mode for x86 processors that goes two levels indirect.

You have to first load the pointer from a memory address and then load indirectly from the pointer.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
0

Try

void test_func(Word32 *var){
    asm( " mov %0, %%edx; \
    addl (%%edx), %%eax" : : "m"(var) );    

    return;
} 
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Sanish
  • 1