https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93934
Bug ID: 93934 Summary: Unnecessary fld of uninitialized float stack variable results in ub of valid C++ code Product: gcc Version: 7.4.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: vajdaz at protonmail dot com Target Milestone: --- Valid C++ code is compiled to assembly that seems to be buggy. Below source code and assembly output of the compiler. Contents of source file float.cc: extern void foo(); extern void bar(); double func(int size, double d, bool b) { double result; for(int i = 0; i < size; ++i) { if (i == 0) { foo(); if(b) result = d; } else { bar(); if(b) result = 42.0; } } return b && size > 0 ? result : 7.0; } I assume that this is valid C++ code. On C++ level there is no way to access an uninitialized variable. The variable result is only used as a return value if size is greater than zero, and b is true, in which case result will be initialized before, for sure. Create assembly: $ g++ -m32 -O2 -fno-pic -fno-omit-frame-pointer -S float.cc Output is: .file "float.cc" .text .p2align 4,,15 .globl _Z4funcidb .type _Z4funcidb, @function _Z4funcidb: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 pushl %edi pushl %esi pushl %ebx subl $44, %esp .cfi_offset 7, -12 .cfi_offset 6, -16 .cfi_offset 3, -20 movl 8(%ebp), %ebx movl 20(%ebp), %eax fldl 12(%ebp) testl %ebx, %ebx fstpl -48(%ebp) movl %eax, -36(%ebp) jle .L2 movl %eax, %esi xorl %edi, %edi .p2align 4,,10 .p2align 3 .L5: testl %edi, %edi je .L16 call _Z3barv movl %esi, %eax testb %al, %al je .L4 flds .LC0 fstpl -32(%ebp) .L4: addl $1, %edi cmpl %edi, %ebx jne .L5 cmpb $0, -36(%ebp) fldl -32(%ebp) je .L17 addl $44, %esp popl %ebx .cfi_remember_state .cfi_restore 3 popl %esi .cfi_restore 6 popl %edi .cfi_restore 7 popl %ebp .cfi_restore 5 .cfi_def_cfa 4, 4 ret .p2align 4,,10 .p2align 3 .L17: .cfi_restore_state fstp %st(0) .L2: flds .LC1 addl $44, %esp popl %ebx .cfi_remember_state .cfi_restore 3 popl %esi .cfi_restore 6 popl %edi .cfi_restore 7 popl %ebp .cfi_restore 5 .cfi_def_cfa 4, 4 ret .p2align 4,,10 .p2align 3 .L16: .cfi_restore_state call _Z3foov fldl -48(%ebp) movl %esi, %eax testb %al, %al fldl -32(%ebp) fcmovne %st(1), %st fstp %st(1) fstpl -32(%ebp) jmp .L4 .cfi_endproc .LFE0: .size _Z4funcidb, .-_Z4funcidb .section .rodata.cst4,"aM",@progbits,4 .align 4 .LC0: .long 1109917696 .align 4 .LC1: .long 1088421888 .ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0" .section .note.GNU-stack,"",@progbits Following things happen in the assembly code when size is 1 and b is true for example. * The value of size is stored into %ebx * The value of b is stored into %eax and -36(%ebp), later in %esi * The value of d is loaded into the fpu register stack and stored into -48(%ebp) We continue straight to the line with "je .L16" and jump to the label .L16. And here comes the interesting part: .L16: .cfi_restore_state call _Z3foov fldl -48(%ebp) <-- loading value of d into fpu movl %esi, %eax testb %al, %al <-- testing if b is true (for fcmovne below) fldl -32(%ebp) <-- loading uninitialized stack variable result fcmovne %st(1), %st <-- choosing which value to put back into result fstp %st(1) fstpl -32(%ebp) jmp .L4 Why we have here a combination of two fldl instructions and a fcmovne? This causes a possible fpu exception if the value of the uninitialized variable named result is accidentally a SNaN value. Which means, the behavior of this code is unpredictable. This can be reproduced with any GCC since 4.4.7, if the -march option is set to something that supports fcmov instructions. I used GCC 7.4.0. Is this a bug, or do I miss something?