https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94113
Bug ID: 94113 Summary: Apparently incorrect register allocation in inline asm when using CMOV. Product: gcc Version: 9.2.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: inline-asm Assignee: unassigned at gcc dot gnu.org Reporter: contact at pgazz dot com Target Milestone: --- Created attachment 48001 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=48001&action=edit The preprocessed C file Version: gcc (Debian 9.2.1-21) 9.2.1 20191130 System type: Debian GNU/Linux bullseye/sid Options and complete command-line: gcc -Wall -Wextra -o broken broken.i Compiler output: no errors or warnings Preprocessed file: broken.i The included example (broken.c) illustrates what appears to be a bug in inline assembly when using the CMOV instruction. Two different C variables are allocated the same register, causing incorrect behavior. __asm__ volatile("movl %3, %0;\n\t" \ "cmpl $0, %1;\n\t" \ "cmovne %2, %0;\n\t" \ : "=r"(final_result) \ : "r"(cond), "r"(if_true), "r"(if_false) \ : "cc" \ ); The inline assembly uses CMOV to assign final_result to either if_true or if_false depending on the cond variable, which is read in from the first command-line argument and converted via atoi. I was expecting the behavior to be equivalent to testing the value of cond, then assigning final_result to either if_true or if_false depending on whether cond is non-zero or zero respectively. But instead, it always assigns it to if_true. Looking at the compiler output in broken.s, the eax register seems to be allocated to both cond and to final_result. In the first line of the inline assembly movl %ecx, %eax; it looks like final_result is eax. But in the next line cmpl $0, %eax; it looks like eax is allocated to cond, which has already been overwritten by the value of if_false in the previous line. movl $0, -16(%rbp) # final_result # ... movl %eax, -12(%rbp) # cond, after atoi movl $333, -8(%rbp) # if_true movl $444, -4(%rbp) # if_false movl -12(%rbp), %eax # cond -> eax movl -8(%rbp), %edx # if_true -> edx movl -4(%rbp), %ecx # if_false -> ecx #APP # 17 "broken.c" 1 movl %ecx, %eax; # move if_false to eax, which cmpl $0, %eax; # should be comparing to the value of cond, not if_false cmovne %edx, %eax; # conditional move if_true to final_result, but condition (not equal) is always true # 0 "" 2 #NO_APP movl %eax, -16(%rbp) # eax -> final_result Is the register allocator somehow assuming that the write-only modifier =r means the value is only written at the end of the inline assembly? I could circumvent it by explicitly using another register (esi) and adding an extra MOV from that register as shown below. __asm__ volatile("movl %3, %%esi;\n\t" \ "cmpl $0, %1;\n\t" \ "cmovne %2, %%esi;\n\t" \ "movl %%esi, %0;\n\t" \ : "=r"(final_result) \ : "r"(cond), "r"(if_true), "r"(if_false) \ : "cc", "esi" \ ); A somewhat similar-looking bug was reported almost 13 years ago, but that particular bug does not seem to happen anymore: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31386 When compiling with optimizations, the bug still exists when running with ./broken 1, which just turns the cond variable to true.