Search code examples
x86clanginline-assembly

Clang errors "expected register" with inline x86 assembly (works with GCC)


I wrote a demo with some inline assembly (showing how to shift an array of memory right one bit) and it compiles and functions fine in GCC. However, the with Clang, I'm not sure if it's generating bad code or what but it's unhappy that I'm using memory despite the "rm" constraint.

I've tried many compilers and versions via Godbolt and while it works on all x86/x86_64 versions of GCC, it fails with all versions of Clang. I'm unsure if the problem is my code or if I found a compiler bug.

Code:

#include <stdio.h>

int main(int argc, char** argv)
{
  unsigned char bytes[16] =
  {
    0xFF, 0x81, 0x81, 0x81,
    0x81, 0x81, 0x81, 0x81,
    0x81, 0x81, 0x81, 0x81,
    0x81, 0x81, 0x81, 0xF0,
  };

  __asm__ volatile("shrl $1, 12(%0); \n"
                   "rcrl $1,  8(%0); \n"
                   "rcrl $1,  4(%0); \n"
                   "rcrl $1,  0(%0); \n"
                   : : "rm" (bytes));

  printf("bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
         bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8],
         bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
  return 0;
}

compilation output:

.<source>:13:20: error: expected register here
.  __asm__ volatile("shrl $1, 12(%0); \n"
.                   ^
.<inline asm>:1:14: note: instantiated into assembly here
.        shrl $1, 12(-88(%rbp)); 
.                    ^
.<source>:14:21: error: expected register here
.                   "rcrl $1,  8(%0); \n"
.                    ^
.<inline asm>:2:13: note: instantiated into assembly here
.rcrl $1,  8(-88(%rbp)); 
.            ^
.<source>:15:21: error: expected register here
.                   "rcrl $1,  4(%0); \n"
.                    ^
.<inline asm>:3:13: note: instantiated into assembly here
.rcrl $1,  4(-88(%rbp)); 
.            ^
.<source>:16:21: error: expected register here
.                   "rcrl $1,  0(%0); \n"
.                    ^
.<inline asm>:4:13: note: instantiated into assembly here
.rcrl $1,  0(-88(%rbp)); 
.            ^

Solution

  • I'm unsure if the problem is my code or if I found a compiler bug.

    The problem is your code. In GNU assembler, parentheses are used to dereference like unary * is in C, and you can only dereference a register, not memory. As such, writing 12(%0) in the assembly when %0 might be memory is wrong. It only happens to work in GCC because GCC chooses to use a register for "rm" there, while Clang chooses to use memory. You should use "r" (bytes) instead.

    Also, you need to tell the compiler that your assembly is going to modify the array, either with a memory clobber or by adding *(unsigned char (*)[16])bytes as an output. Right now, it's allowed to optimize your printf to just hardcode what the values were at the beginning of the program.

    Fixed code:

    #include <stdio.h>
    
    int main(int argc, char** argv)
    {
      unsigned char bytes[16] =
      {
        0xFF, 0x81, 0x81, 0x81,
        0x81, 0x81, 0x81, 0x81,
        0x81, 0x81, 0x81, 0x81,
        0x81, 0x81, 0x81, 0xF0,
      };
    
      __asm__ volatile("shrl $1, 12(%1); \n"
                       "rcrl $1,  8(%1); \n"
                       "rcrl $1,  4(%1); \n"
                       "rcrl $1,  0(%1); \n"
                       : "+m" (*(unsigned char (*)[16])bytes) : "r" (bytes));
    
      printf("bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",
             bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8],
             bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
      return 0;
    }