public inbox for gcc@gcc.gnu.org
 help / color / mirror / Atom feed
* Are some builtin functions (for example log() vs. sqrt()) more equal than others?
@ 2021-07-30 15:30 Stefan Kanthak
  2021-07-30 16:33 ` Joseph Myers
  0 siblings, 1 reply; 5+ messages in thread
From: Stefan Kanthak @ 2021-07-30 15:30 UTC (permalink / raw)
  To: gcc

Hi @ll,

both the IEEE 754 and the ISO C standards define the (constant) expressions
1.0/0.0 and 0.0/0.0 to yield the floating-point constants INFINITY and NAN
alias INDEFINITE, to be provided as macros (ISO/IEC 9899:1999 7.12/4 INFINITY
and 7.12/5 NAN) in math.h.

JFTR: the statement "NAN is a GNU extension" given in
      <https://www.gnu.org/software/libc/manual/html_node/Infinity-and-NaN.html>
      is WRONG since the last millenium!

The ISO C standard also defines the behaviour of standard functions like
log() and their (constant and well-known) result for (constant) arguments
like -0.0, +0.0, -INFINITY, +INFINITY and NAN

GCC defines most of these standard functions as builtins, for example
log(), and evaluates them at compile time for constant arguments.

GCC but refuses the definition of constants using for example log(-0.0),
although the resulting function value is a well-defined constant!

--- bug.c ---
#include <math.h>

const double euler = log(1.0);
const double infinity = log(-0.0);
const double indefinite = log(-1.0);
const double log_euler = log(euler);
const double log_e = log(log(1.0));
const double log_phi = log(sqrt(5.0) * 0.5 + 0.5);
const double log_minusinf = log(-1.0 / 0.0);
const double log_plusinf = log(1.0 / 0.0);
const double log_nan = log(0.0 / 0.0);
--- EOF ---

$ gcc bug.c
bug.c:4:25: error: initializer element is not constant
    4 | const double infinity = log(-0.0);
      |                         ^~~
bug.c:5:27: error: initializer element is not constant
    5 | const double indefinite = log(-1.0);
      |                           ^~~
bug.c:7:26: error: initializer element is not constant
    6 | const double log_euler = log(euler);
      |                          ^~~
bug.c:9:22: error: initializer element is not constant
    7 | const double log_e = log(log(1.0));
      |                      ^~~
bug.c:11:29: error: initializer element is not constant
    9 | const double log_minusinf = log(-1.0 / 0.0);
      |                             ^~~
bug.c:12:28: error: initializer element is not constant
   10 | const double log_plusinf = log(1.0 / 0.0);
      |                            ^~~
bug.c:13:24: error: initializer element is not constant
   11 | const double log_nan = log(0.0 / 0.0);
      |                        ^~~

Especially compare the lines 7 and 8: while GCC fails on
log(log(1.0)) it but succeeds on log(sqrt(5.0) * 0.5 + 0.5)!

NOT amused
Stefan Kanthak

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Are some builtin functions (for example log() vs. sqrt()) more equal than others?
  2021-07-30 15:30 Are some builtin functions (for example log() vs. sqrt()) more equal than others? Stefan Kanthak
@ 2021-07-30 16:33 ` Joseph Myers
  2021-07-30 19:53   ` Stefan Kanthak
  0 siblings, 1 reply; 5+ messages in thread
From: Joseph Myers @ 2021-07-30 16:33 UTC (permalink / raw)
  To: Stefan Kanthak; +Cc: gcc

None of these are valid constant expressions as defined by the standard 
(constant expressions cannot involve evaluated function calls).  Some 
might be accepted as an extension, but I expect that since the 
optimization for constant arguments is intended for valid calls that would 
otherwise be executed at runtime, not for static initializers, it's 
avoiding optimizations that would result in the loss of floating-point 
exception flag raising.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Are some builtin functions (for example log() vs. sqrt()) more equal than others?
  2021-07-30 16:33 ` Joseph Myers
@ 2021-07-30 19:53   ` Stefan Kanthak
  2021-07-30 20:51     ` Joseph Myers
  0 siblings, 1 reply; 5+ messages in thread
From: Stefan Kanthak @ 2021-07-30 19:53 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc

Joseph Myers <joseph@codesourcery.com> wrote:

> None of these are valid constant expressions as defined by the standard 
> (constant expressions cannot involve evaluated function calls).

That's why I ask specifically why GCC bugs on log(log(...)), but not on
log(sqrt(...) ...)!

GCC also accepts following initializers and places these constants in the
    .rodata section, i.e. it evaluates the functions during compile time:

const double pi = acos(-1.0);
const double pi_by_2 = acos(0.0);
const double pi_by_two = asin(1.0);
const double log_pi = log(acos(-1.0));
const double log_pi_by_2 = log(acos(0.0));
const double log_pi_by_two = log(asin(1.0));
const double sqrt_sqrt_2 = sqrt(sqrt(2.0));

Again: what's SOOO special about log(log(<constant expression>))? 

> Some might be accepted as an extension, but I expect that since the 
> optimization for constant arguments is intended for valid calls that
> would otherwise be executed at runtime, not for static initializers,
> it's avoiding optimizations that would result in the loss of floating-
> point exception flag raising.

That's no valid excuse: by the standard, the compiler is free to execute
static initializers during runtime, before calling the main() routine.

JFTR: doing so would but inhibit the placement of such constants in the
      read-only data section ... what is also allowed by the standard.

regards
Stefan

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Are some builtin functions (for example log() vs. sqrt()) more equal than others?
  2021-07-30 19:53   ` Stefan Kanthak
@ 2021-07-30 20:51     ` Joseph Myers
  2021-07-30 21:54       ` Stefan Kanthak
  0 siblings, 1 reply; 5+ messages in thread
From: Joseph Myers @ 2021-07-30 20:51 UTC (permalink / raw)
  To: Stefan Kanthak; +Cc: gcc

On Fri, 30 Jul 2021, Stefan Kanthak wrote:

> Joseph Myers <joseph@codesourcery.com> wrote:
> 
> > None of these are valid constant expressions as defined by the standard 
> > (constant expressions cannot involve evaluated function calls).
> 
> That's why I ask specifically why GCC bugs on log(log(...)), but not on
> log(sqrt(...) ...)!

The log(log(1.0)) example you gave would raise divide-by-zero.

> > Some might be accepted as an extension, but I expect that since the 
> > optimization for constant arguments is intended for valid calls that
> > would otherwise be executed at runtime, not for static initializers,
> > it's avoiding optimizations that would result in the loss of floating-
> > point exception flag raising.
> 
> That's no valid excuse: by the standard, the compiler is free to execute
> static initializers during runtime, before calling the main() routine.

The point of this extension isn't to accept as much as possible.  Rather, 
it turned out when I implemented standard constant expression rules for 
GCC 4.5 that lots of existing code was using just about anything GCC could 
fold into a constant in just about any context requiring a constant 
expression.  So for compatibility with existing, questionable, pre-GCC-4.5 
code, we still allow "expressions that can be folded into a constant" in 
various such contexts, with a pedwarn-if-pedantic.  But because this isn't 
a designed, documented extension or something it's actually considered 
good practice to use, the semantics remain "expressions that can be folded 
into a constant", with all the dependence that implies on the folding GCC 
does for optimization purposes - and that folding is designed for 
optimizing code outside of static initializers, not for use in this 
extension, with all the corresponding implications for its design.

So if some expression doesn't get folded to a constant outside of static 
initializers (or for that matter, if it does get so folded, but previous 
GCC versions didn't accept it in static initializers, since this extension 
is about compatibility with existing code), it's not a bug for it not to 
be accepted in a static initializer.

If, outside of static initializers, some of these expressions don't get 
folded to a constant *even with -fno-trapping-math*, that's a missed 
optimization and it would make sense to improve the compiler to fold them 
given -fno-trapping-math.

Executing static initializers at runtime seems more like a C++ thing; it's 
not within the conventional concept of how C maps to object files.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Are some builtin functions (for example log() vs. sqrt()) more equal than others?
  2021-07-30 20:51     ` Joseph Myers
@ 2021-07-30 21:54       ` Stefan Kanthak
  0 siblings, 0 replies; 5+ messages in thread
From: Stefan Kanthak @ 2021-07-30 21:54 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc

"Joseph Myers" <joseph@codesourcery.com> wrote:

> On Fri, 30 Jul 2021, Stefan Kanthak wrote:
> 
>> Joseph Myers <joseph@codesourcery.com> wrote:
>> 
>> > None of these are valid constant expressions as defined by the standard 
>> > (constant expressions cannot involve evaluated function calls).
>> 
>> That's why I ask specifically why GCC bugs on log(log(...)), but not on
>> log(sqrt(...) ...)!
> 
> The log(log(1.0)) example you gave would raise divide-by-zero.

ARGH, my SILLY fault: I named the first constant "euler" since it should
of course be set to 2.71..., i.e. this line should have read

const double euler = exp(1.0);

Unfortunately I wrote but log(1.0) there and introduced the divide-by-zero.
Sorry for the confusion.

JFTR; in order to provide a short repro, I stripped down a larger program
      where the values of a

static const double table[] = {log2(0.0), ...};

      were to be initialized with several logarithms, which but failed.
      And while stripping it down, I introduced this bug.-(

>> > Some might be accepted as an extension, but I expect that since the 
>> > optimization for constant arguments is intended for valid calls that
>> > would otherwise be executed at runtime, not for static initializers,
>> > it's avoiding optimizations that would result in the loss of floating-
>> > point exception flag raising.
>> 
>> That's no valid excuse: by the standard, the compiler is free to execute
>> static initializers during runtime, before calling the main() routine.
> 
> The point of this extension isn't to accept as much as possible.

I expected it the other way 'round.

> Rather, it turned out when I implemented standard constant expression rules 
> for GCC 4.5 that lots of existing code was using just about anything GCC 
> could fold into a constant in just about any context requiring a constant 
> expression.  So for compatibility with existing, questionable, pre-GCC-4.5 
> code, we still allow "expressions that can be folded into a constant" in 
> various such contexts, with a pedwarn-if-pedantic.  But because this 
> isn't a designed, documented extension or something it's actually considered 
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OK. I searched the docs of course before I wrote in, and only asked because
I couldn't find anything about this (non-standard) feature.

> good practice to use, the semantics remain "expressions that can be folded 
> into a constant", with all the dependence that implies on the folding GCC 
> does for optimization purposes - and that folding is designed for 
> optimizing code outside of static initializers, not for use in this 
> extension, with all the corresponding implications for its design.
> 
> So if some expression doesn't get folded to a constant outside of static 
> initializers (or for that matter, if it does get so folded, but previous 
> GCC versions didn't accept it in static initializers, since this extension 
> is about compatibility with existing code), it's not a bug for it not to 
> be accepted in a static initializer.

Thanks for the clarification.

> If, outside of static initializers, some of these expressions don't get 
> folded to a constant *even with -fno-trapping-math*, that's a missed 
> optimization and it would make sense to improve the compiler to fold them 
> given -fno-trapping-math.

In order to get the larger program compiled, I defined the table inside
the function which consumed it:

double function(...)
{
    const double table[] = {log2(0.0), ...};

    ...
}

GCC evaluated almost all logarithms during compile time and placed them
in the .rodata section.

BUT: instead to transfer these precomputed constants in the prolog of
     this function with a call to memcpy() from .rodata to the stack,
     GCC created a whole lot of

     movq  .LC1...(%rip), %xmm0
     movq  %xmm0, offset(%rsp)

     instruction pairs ... one for each precomputed constant.
     I expected GCC to be a LITTLE smarter there...

> Executing static initializers at runtime seems more like a C++ thing; it's 
> not within the conventional concept of how C maps to object files.

regards
Stefan

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2021-07-30 21:55 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-30 15:30 Are some builtin functions (for example log() vs. sqrt()) more equal than others? Stefan Kanthak
2021-07-30 16:33 ` Joseph Myers
2021-07-30 19:53   ` Stefan Kanthak
2021-07-30 20:51     ` Joseph Myers
2021-07-30 21:54       ` Stefan Kanthak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).