Related constant folding of floating point values

Hi all,

I have the following test case:
#define FLT_EPSILON 1.19209290E-7

int err = -1;
int main()
{
float a = 8.1;
if (((a - 8.1) >= FLT_EPSILON) || ((a - 8.1) <= -FLT_EPSILON)) { //I am
using FLT_EPSILON to check whether (a != 2.0).
    err = 1;
  } else {
    err = 0;
  }
  return 0;
}

with -O3 optimization level clang generates already incorrect LLVM IR:
; Function Attrs: nounwind uwtable
define i32 @main() #0 {
entry:
  store i32 1, i32* @err, align 4, !tbaa !0
  ret i32 0
}

BUT if I change the value of the variable 'a' to be '7.1'. then a correct IR
is generated:
...
entry:
  store i32 0, i32* @err, align 4, !tbaa !0
  ret i32 0
...

I have already investigated the issue and found that during the EarlyCSE
transformation it seems replaces the FPExt instruction with an incorrect
hexadecimal value:
The LLVM IR generated with O0 opt. level is:
...
  store float 0x4020333340000000, float* %a, align 4
  %0 = load float* %a, align 4
  %conv = fpext float %0 to double
  %sub = fsub double %conv, 8.100000e+00
  %cmp = fcmp oge double %sub, 0x3E8000000102F4FD
  br i1 %cmp, label %if.then, label %lor.lhs.false

lor.lhs.false: ; preds = %entry
  %1 = load float* %a, align 4
  %conv2 = fpext float %1 to double
  %sub3 = fsub double %conv2, 8.100000e+00
  %cmp4 = fcmp ole double %sub3, 0xBE8000000102F4FD
  br i1 %cmp4, label %if.then, label %if.else
...

during the transformation the %conv is replaced with "double
0x4020333340000000" and then the result of comparison is resolved
incorrectly.

Is not this a bug?
I have also searched for a similar issue in llvm bug list, but could not
found anything.

Thank you,
Arsen

Hi Arsen,

Hi all,

I have the following test case:
#define FLT_EPSILON 1.19209290E-7

int err = -1;
int main()
{
float a = 8.1;
if (((a - 8.1) >= FLT_EPSILON) || ((a - 8.1) <= -FLT_EPSILON)) { //I am
using FLT_EPSILON to check whether (a != 2.0).

It’s not clear what this comment refers to, but it doesn’t seem to be related to this code.

   err = 1;
} else {
   err = 0;
}
return 0;
}

with -O3 optimization level clang generates already incorrect LLVM IR:
; Function Attrs: nounwind uwtable
define i32 @main() #0 {
entry:
store i32 1, i32* @err, align 4, !tbaa !0
ret i32 0
}

BUT if I change the value of the variable 'a' to be '7.1'. then a correct IR
is generated:
...
entry:
store i32 0, i32* @err, align 4, !tbaa !0
ret i32 0
...

I have already investigated the issue and found that during the EarlyCSE
transformation it seems replaces the FPExt instruction with an incorrect
hexadecimal value:
The LLVM IR generated with O0 opt. level is:
...
store float 0x4020333340000000, float* %a, align 4
%0 = load float* %a, align 4
%conv = fpext float %0 to double
%sub = fsub double %conv, 8.100000e+00
%cmp = fcmp oge double %sub, 0x3E8000000102F4FD
br i1 %cmp, label %if.then, label %lor.lhs.false

lor.lhs.false: ; preds = %entry
%1 = load float* %a, align 4
%conv2 = fpext float %1 to double
%sub3 = fsub double %conv2, 8.100000e+00
%cmp4 = fcmp ole double %sub3, 0xBE8000000102F4FD
br i1 %cmp4, label %if.then, label %if.else
...

during the transformation the %conv is replaced with "double
0x4020333340000000" and then the result of comparison is resolved
incorrectly.

Is not this a bug?

No. The issue is that you are taking a double (8.1), converting it to float, and then subtracting the original double from it. The rounding error introduced from the conversion is larger than the epsilon value that you are comparing to, so the first comparison (a-8.1 >= FLT_EPSILON) is always true.

Mark

Hi Arsen,

Yes, my bad, FLT_EPSILON ends up as a double constant, so not denormalized,
but the error introduced is larger than epsilon anyway.

--renato

Thank you all for your responses.

Just one point I have not understand well. Lets look at this part of IR
code:

store float 0x4020333340000000, float* %a, align 4
%0 = load float* %a, align 4
%conv = fpext float %0 to double
%sub = fsub double %conv, 8.100000e+00

Here the float value is represented by hexadecimal constant value of float
type ( float 0x4020333340000000). If I have correctly understood the same
hex value can be converted to different result of float or double types
because the IEEE 754 standard specifies a binary32 as having:
(single-precision binary floating-point format)
Sign bit: 1 bit
Exponent width: 8 bits
Significand precision: 24 (23 explicitly stored)

and

a binary64 as having: (double-precision binary floating-point format)
Sign bit: 1 bit
Exponent width: 11 bits
Significand precision: 53 bits (52 explicitly stored)

After this all, I think that the optimizations should not replace the
conversation:
%conv = fpext float 0x4020333340000000 to double,
with
double 0x4020333340000000
Please note, I have got during the debugging that EarlyCSE transformation
does this.

I think that it should be another constant hexadecimal value when its type
is already a 'double'.

Am I misunderstood anything?

Thanks,
Arsen