3

What is the behavior of a asm() block of code in architectures where it is unused but not supported? For example, what would happen if I compile the following code on ARM

if (false) {
    asm volatile("x86specificinstruction" ...);
}

Is this valid C++? Will the code compile? Does the standard say anything about situations like this?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Curious
  • 20,870
  • 8
  • 61
  • 146
  • 8
    IIRC everything about `asm` in the standard says it has implementation defined behavior. – NathanOliver Aug 22 '18 at 19:21
  • 2
    With optimization enabled, the asm will be removed so no harm done. Without optimization, code will compile but not assemble. That's assuming your compiler at least understands the `asm`. – Jester Aug 22 '18 at 19:21
  • So from my understanding, this sort of thing *must* be gated with a preprocessor check. And compile time checks like SFINAE, `if constexpr` are basically "implementation defined"? – Curious Aug 22 '18 at 19:22
  • 3
    `if constexpr` should be fine as it will discard the statement (won't compile it). – NathanOliver Aug 22 '18 at 19:23
  • So I guess the same applies for SFINAE? – Curious Aug 22 '18 at 19:24
  • @NathanOliver could you link to a section in the standard that explains this? – Curious Aug 22 '18 at 19:24
  • 1
    @Jester: turns out that it's fine even with `-O0` on gcc and clang. Apparently they don't generate code inside an `if(false)`, so you can't `jump` to those source lines in GDB. – Peter Cordes Aug 22 '18 at 19:47
  • @PeterCordes Even on conditions that aren't clearly false, I'm pretty sure that GCC doesn't support jumping in the middle of a section. – curiousguy Jan 21 '19 at 06:16
  • @curiousguy: yes it does, that's part of why `-O0` code-gen is such garbage: each C statement is compiled to a separate block of asm, exactly so that GDB's `jump` command will have the same effect as jumping in the C abstract machine *within the same function*. (As well modification of any variable by the debugger when stopped at a breakpoint on any source line). [Is it possible to "jump"/"skip" in GDB debugger?](https://stackoverflow.com/a/46043760) and [Why does clang produce inefficient asm for this simple floating point sum (with -O0)?](https://stackoverflow.com/q/53366394) – Peter Cordes Jan 21 '19 at 12:59
  • The only restriction is that you jump to/from asm addresses that correspond to C source lines, not the middle of a block that implements one statement, and not to the middle of the function prologue or something. – Peter Cordes Jan 21 '19 at 13:02

2 Answers2

5

It is up to the implementation on how this will be handled. From [dcl.asm]

An asm declaration has the form

asm-definition:
    attribute-specifier-seq opt asm ( string-literal ) ;

The asm declaration is conditionally-supported; its meaning is implementation-defined. The optional attribute-specifier-seq in an asm-definition appertains to the asm declaration. [ Note: Typically it is used to pass information through the implementation to an assembler. — end note ]

emphasis mine

That said you can put the code in a #if #endif , use SFINAE, or use if constexpr. All of those will remove the code from compiling if the condition is not satisfied.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • If `asm` is not supported, it it still recognized a keyword? – François Andrieux Aug 22 '18 at 19:29
  • @FrançoisAndrieux It is still a keyword: https://timsong-cpp.github.io/cppwp/lex.key – NathanOliver Aug 22 '18 at 19:30
  • 1
    Visual Studio recognizes `asm` but invariably produces a compilation error (`error C2290: C++ 'asm' syntax ignored. Use __asm.`) when it's encountered. I couldn't test `if constexpr` but putting it behind a `enable_if_t` disabled function worked fine. I was actually surprised at how much nonsense I could pack into that function without causing any compilation errors. – François Andrieux Aug 22 '18 at 20:20
  • @FrançoisAndrieux That's MSVS for you. Their conformance is a lot better than it used to be but it still has some holes. I do love it's debugger though. What version are you using? – NathanOliver Aug 22 '18 at 20:23
  • I tried with 2015 and 2017 and got the same result with both. – François Andrieux Aug 22 '18 at 20:27
  • @FrançoisAndrieux Yep. Looks like MSVS doesn't support `asm` at all. – NathanOliver Aug 22 '18 at 20:29
  • @FrançoisAndrieux Historically some C++ compilers were known to accept nearly anything made of valid tokens in a template that wasn't instantiated, as long as the {} balance was good. (Accented characters outside a literal would cause a parse error obviously.) – curiousguy Jan 21 '19 at 06:37
2

Yes, it's fine on gcc and clang, as long as the false is definitely a compile-time constant. It won't compile on compilers like MSVC that don't support asm(""); at all.

I wouldn't recommend it, though. It's much more normal to use the preprocessor for arch-specific and implementation-specific code selection.


ISO C++ has basically nothing to say about it. asm is an implementation-specific extension, so yes an implementation would be free to reject asm that didn't assemble, even in dead code. But the question is whether the compilers we actually use are like that.

The only meaningful question is what happens on actual compilers you care about.


On MSVC for example, asm(""); is not supported syntax, regardless of the contents of the string literal. So it's a syntax error at compile time everywhere. You can't use if(false){} to protect code that doesn't even compile, just like you can't do so in template metaprogramming (unfortunately).


On compilers that support GNU extensions (the most widely used extensions to ISO C or C++ that include an asm("") style of syntax), let's have a look on the Godbolt compiler explorer.

I picked mfence because it's a valid instruction that doesn't need any input or output operands to make sense, because it doesn't modify any registers. And unlike nop or something, it won't assemble on non-x86. (volatile is implicit for a GNU extended-asm statement with no output operands.)

void nobarrier() {
    //int condition = 0;  if(condition) // wouldn't work at -O0
    if(false) {
        // only an error with MSVC
        asm("mfence" ::: "memory");
    }
}

#ifdef UNCONDITIONAL
void barrier() {
    // gcc compiles separately from assembling
    // clang/LLVM's built-in assembler chokes at compile time
    asm("mfence" ::: "memory");
}
#endif

GCC and clang don't even try to assemble the asm() statement inside the if(false), even with optimization disabled.

Of course, -O0 stops them from treating int cond=0; if(cond){} as always false, because for consistent debugging they make asm that assumes every variable was modified in memory (by a debugger) between every statement. (All the stores/reloads is partly why un-optimized code is so slow).

With ARM or AArch64 gcc, nobarrier() compiles just fine, to asm that will assemble just fine. e.g.

@ gcc7.2 -O3 for ARM32.  Similar output with -O0
nobarrier():
    bx      lr

GCC doesn't even try to parse the contents of the asm template, so it will compile barrier() for AArch64, but it includes an instruction that won't assemble.

@ gcc6.3 -O0 -DUNCONDITIONAL
barrier():
    mfence
    nop
    ret

If you care about the distinction between compiling and assembling, clang is different.

clang6.0 -O0 -target mips -DUNCONDITIONAL says

<source>:13:9: error: unknown instruction
    asm("mfence" ::: "memory");
        ^

<inline asm>:1:2: note: instantiated into assembly here
        mfence
        ^
1 error generated.
Compiler returned: 1

This doesn't matter if you only care about C++ -> object file, not making .s asm source output. For that, it behaves the same as gcc. if(false) does protect an asm statement with non-MIPS instructions from being parsed:

@ clang6.0 -O0 -target mips
nobarrier():                          # @nobarrier()
    addiu   $sp, $sp, -8
    sw      $fp, 4($sp)             # 4-byte Folded Spill
    move    $fp, $sp
    move    $sp, $fp
    lw      $fp, 4($sp)             # 4-byte Folded Reload
    addiu   $sp, $sp, 8
    jr      $ra
    nop
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847