Home > Software engineering >  Input Operand `"m"(var)` and Output Operand `"=m"(var)` in GNU C inline asm? Use
Input Operand `"m"(var)` and Output Operand `"=m"(var)` in GNU C inline asm? Use

Time:01-17

I wonder what input operand "m"(var) and output operand "=m"(var) in asm do:

asm volatile("" : "=m"(blk) : :);
asm volatile("" : : "m"(write_idx), "m"(blk) :);

I ran into the 2 lines above here, in a SPMC queue.

And what are the side effects? The lines above have no asm instruction, and so I believe the author was trying to utilize some well defined side effects (e.g. flushing the values of write_idx and blk hold by registers if they are in the 2nd line?)

CodePudding user response:

asm volatile("" : "=m"(blk) : :);

After this statement, the compiler believes that some random assembly code has written to blk. As an output operand was given, the compiler assumes that the previous value of blk doesn't matter and can discard code that assigned a value to blk that nobody read before this statement. It also must assume that blk now holds some other value and read that value back from memory instead of e.g. from a copy in registers.

So shortly, this looks like an attempt to force the compiler into treating blk as if it was assigned to from a source unkown to the compiler. This is usually better achieved by qualifying blk as volatile.

Note that as the asm statement is qualified volatile, the statement additionally implies an ordering requirement to the compiler, preventing certain code rearrangements.

Similar code (though without the volatile) could also be used to tell the compiler that you want an object to assume an unspecified value as an optimisation. I recommend against this sort of trick though.

asm volatile("" : : "m"(write_idx), "m"(blk) :);

The compiler assumes that this statement reads from the variables write_idx and blk. Therefore, it must materialise the current contents of these variables into memory before it can execute the statement. This looks like a crude approximation of a memory barrier but does not actually effect a memory barrier as was likely intended.


Aside from this, the code you have shown must certainly be defective: C objects may unless specified otherwise not be modified concurrently without synchronisation. However, this code cannot have such synchronisation as it does not include any headers with types (such as std::atomic) or facilities (such as mutexes) for synchronisation.

Without reading the code in detail, I suppose that the author just uses ordinary objects for synchronisation, believing concurrent mutation to be well-defined. Observing that it is not, the author likely believed that the compiler was to blame and added these asm statements as kludges to make it generate code that seems to work. However, this is incorrect though it may appear to work on architectures with sufficiently strict memory models (such as x86) and with sufficiently stupid compilers or by sheer luck.

Do not program like this. Use atomics or high level synchronisation facilities instead.

CodePudding user response:

The first one sounds like a bug to me. An asm output operand must always be written to. The compiler will assume any stores to blk before that can be optimized out because the asm block will set a new value. Example:

extern int foo;
int bar()
{
    auto& blk = foo;
    asm volatile("" : "=m"(blk) : :);
    return blk;
}

int baz()
{
    foo = 42;
    return bar();
}

Compiling with gcc 12.2 at -O2 will produce:

baz():
        movl    foo(%rip),            
  • Related