gcc compiler option -Ofast used when compiling Ruby implementation (using C code) maybe gives bugs?

I intend to ask the following on Ruby-Core - or maybe it should be on
Ruby-Dev? - but I thought I'd try Ruby-Talk first:

I've been looking at ways of making the Date class faster, mostly by
replacing its use of floating point arithmetic by faster integer
arithmetic, but also in some cases by changing the algorithms. (Taken
together, these seem to reduce the time taken for some commonly used Date
functions to 30% - or less - of the time taken for the current C
implementation of Date.)

To test these I wrote a C program to compare the current Date C functions
in "date_core.c" with my versions. I compiled and ran this program on
Microsoft Windows 7 with MinGW gcc, and on Lubuntu Linux with GNU gcc.

On each platform I tried using different optimization options.
Using -O0, -O1, -O2, -O3, -Og, -Os the results were the same for the
current Date C functions and my versions.

But when I used -Ofast then for some functions I was getting different
results from the current Date C functions and my versions.

After quite a bit of effort I narrowed down the problem to two types of
statements. In each case with "-Ofast", whether or not these statements
worked as intended and expected might depend on the order in which things
were done, and also - and very weirdly? - on how apparently quite unrelated
statements were written: for example, in one case with MinGW on Microsoft
Windows 7, whether or not the second statement for "x" gave the correct
value depended on whether an apparently completely unrelated "printf"
statement had one or two arguments! The two statements were:

* int y = an integer value;
  double a = floor(y / 100.0);
  This is used in function "c_civil_to_jd" in "date_core.c".
  It worked as expected, except that with the "-Ofast" option
  when y < 0 and y was an exact multiple of 100,
  sometimes the result was reproducibly and consistently floor(y / 100) - 1;

* int jd = an integer value;
  double x = floor((jd - 1867216.25) / 36524.25);
  This is used in function "c_jd_to_civil" in "date_core.c".
  It worked as expected, except that with the "-Ofast" option
  when jd = Julian Day number of Gregorian y-03-01
  with y >= 700 and y % 400 == 3,
  then sometimes the result was reproducibly and consistently
  floor((jd - 1867216.25) / 36524.25) - 1.

This has made me more than somewhat suspicious of using the gcc compiler
option -Ofast (I've submitted these as possible bugs to MinGW), and I
wonder if people compiling Ruby (Ruby itself and/or C extensions) have used
-Ofast as a compiler option, and if they have, have they experienced any
weird behaviour?

Oops: near the end "with y >= 700 and y % 400 == 3" should be "with y >=
700 and y % 400 == 300".

···

On Tue, Nov 5, 2019 at 4:58 PM Colin Bartlett <colinb2r@googlemail.com> wrote:

I intend to ask the following on Ruby-Core - or maybe it should be on
Ruby-Dev? - but I thought I'd try Ruby-Talk first:

I've been looking at ways of making the Date class faster, mostly by
replacing its use of floating point arithmetic by faster integer
arithmetic, but also in some cases by changing the algorithms. (Taken
together, these seem to reduce the time taken for some commonly used Date
functions to 30% - or less - of the time taken for the current C
implementation of Date.)

To test these I wrote a C program to compare the current Date C functions
in "date_core.c" with my versions. I compiled and ran this program on
Microsoft Windows 7 with MinGW gcc, and on Lubuntu Linux with GNU gcc.

On each platform I tried using different optimization options.
Using -O0, -O1, -O2, -O3, -Og, -Os the results were the same for the
current Date C functions and my versions.

But when I used -Ofast then for some functions I was getting different
results from the current Date C functions and my versions.

After quite a bit of effort I narrowed down the problem to two types of
statements. In each case with "-Ofast", whether or not these statements
worked as intended and expected might depend on the order in which things
were done, and also - and very weirdly? - on how apparently quite unrelated
statements were written: for example, in one case with MinGW on Microsoft
Windows 7, whether or not the second statement for "x" gave the correct
value depended on whether an apparently completely unrelated "printf"
statement had one or two arguments! The two statements were:

* int y = an integer value;
  double a = floor(y / 100.0);
  This is used in function "c_civil_to_jd" in "date_core.c".
  It worked as expected, except that with the "-Ofast" option
  when y < 0 and y was an exact multiple of 100,
  sometimes the result was reproducibly and consistently floor(y / 100) -
1;

* int jd = an integer value;
  double x = floor((jd - 1867216.25) / 36524.25);
  This is used in function "c_jd_to_civil" in "date_core.c".
  It worked as expected, except that with the "-Ofast" option
  when jd = Julian Day number of Gregorian y-03-01
  with y >= 700 and y % 400 == 3,
  then sometimes the result was reproducibly and consistently
  floor((jd - 1867216.25) / 36524.25) - 1.

This has made me more than somewhat suspicious of using the gcc compiler
option -Ofast (I've submitted these as possible bugs to MinGW), and I
wonder if people compiling Ruby (Ruby itself and/or C extensions) have used
-Ofast as a compiler option, and if they have, have they experienced any
weird behaviour?

Colin Bartlett wrote:

I've been looking at ways of making the Date class faster, mostly
by replacing its use of floating point arithmetic by faster integer
arithmetic, but also in some cases by changing the algorithms.

Cool :slight_smile:

On each platform I tried using different optimization options. Using
-O0, -O1, -O2, -O3, -Og, -Os the results were the same for the current
Date C functions and my versions.

But when I used -Ofast then for some functions I was getting different
results from the current Date C functions and my versions.

That could be normal for -Ofast:

-Ofast: Disregard strict standards compliance... also enables
optimizations that are not valid for all standard-compliant
programs... turns on -ffast-math.

-ffast-math: ... can result in incorrect output for programs that
depend on an exact implementation... It may, however, yield faster
code for programs that do not require the guarantees of these
specifications.