Bug 78851 - Resolve DR 550 in cmath and continue using __builtin_powil() even in C++11
Summary: Resolve DR 550 in cmath and continue using __builtin_powil() even in C++11
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 6.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-12-18 17:02 UTC by Vadim Zeitlin
Modified: 2021-11-30 04:48 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Vadim Zeitlin 2016-12-18 17:02:50 UTC
It looks like the `long double pow(long double, int)` overload in `<cmath>` was disabled because of the [DR 550](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550) to the C++11 standard which proposed removing these overloads. However [this answer](http://stackoverflow.com/a/5627278/15275) by Howard Hinnant, who submitted the DR in the first place, indicates that these overloads were kept in the final version of the standard and so, AFAICS, shouldn't be disabled for C++11 in the header.

One reason for enabling them would be (presumably, I didn't measure it, but #11706 wouldn't have been done without it, I think) to improve performance but another one, which is actually the one that led me to open this bug, is to avoid gratuitous inconsistencies between C++03 and C++11.

See also #70299.
Comment 1 Marc Glisse 2016-12-18 22:02:20 UTC
You are misinterpreting Howard's answer. Please show code that currently doesn't compile and you would like to be able to compile, or code that compiles (specify the flags used) to asm that isn't as fast as you would like.
Comment 2 Vadim Zeitlin 2016-12-19 00:29:46 UTC
Sorry if I misunderstood but what exactly am I misinterpreting? Looking at the code (and comment) at https://github.com/gcc-mirror/gcc/blob/6514c52372bb36e555843b0419d71cf55eda0409/libstdc++-v3/include/c_global/cmath#L395 I thought that this check was supposed to be temporary and could be removed now that the DR 550 has been resolved without requiring these functions to be removed. If this is supposed to be the permanent solution then at least the comment and the defect marker should be removed, shouldn't it?

However it seems to me that it would be better to remove the check. Not because some code doesn't currently compile, but because the generated code with -std=c++11 is different (certainly) and suboptimal (probably) than with -std=c++98. E.g. if you compile and run this program

---------------------------------- >8 --------------------------------------
#include <cmath>
#include <stdio.h>

void f(long double x) {
    int n = -10;

    printf("%.40Lf\n", std::pow(x, n));
}

int main() {
    f(10.0L);
}
---------------------------------- >8 --------------------------------------

with gcc version 6.2.0 (i686-win32-sjlj-rev1, Built by MinGW-W64 project),
it will output 0.0000000001000000000000000000018377930496 in C++98 mode but 0.0000000001000000000000000000397031165002 in C++11.

Moreover, looking at the generated assembly, in C++98 the result is computed directly with a few multiplication and a single inverse operation, which seems to be consistent with __builtin_powil() being used. However in C++11 mode there is a call to _powl() instead.

Wouldn't it be better to continue to use __builtin_powil() even in C++11? If not, this should be just closed, of course, but I'd appreciate it if you could please explain why. TIA!
Comment 3 Marc Glisse 2016-12-19 05:28:32 UTC
On linux with glibc, both versions output the same number, so your libm is to blame. Normally, the int version, implemented with multiplications, may be less accurate than the general version. And gcc does generate multiplications for you if you pass it the flag -ffast-math (probably something a little weaker is sufficient), meaning that you are ok with the slight loss of precision as long as the program is faster. Depending on the flags (-fwhole-program or -flto for instance), you may even get f inlined and the result of pow is computed at compile-time by MPFR ;-)

The template version right below in cmath catches the int case, uses __builtin_powl, which gcc's optimizers know they can replace with __builtin_powil if the second argument is converted from an integer and the circumstances are right (command-line flags). I don't see what in the comment in cmath makes it look like a temporary patch, we tend to add those DR markers as documentation of past changes, not as FIXME. Actually, I guess we could remove those overloads completely, even in C++03, since we have the template version. But it might confuse users who were relying on the performance benefit (and relaxed accuracy) in C++03 even without suitable command-line flags. Everyone should move to C++14 anyway...
Comment 4 Vadim Zeitlin 2016-12-19 13:52:44 UTC
Thanks for the explanation! I didn't realize the template function below was smart enough to select __builtin_powil() automatically, this is quite impressive (although it doesn't happen in my particular case...). Also, I thought the comment was temporary because of the question in it: "What should the return type of pow(float,int) be?", which seemed to require an answer ("double"), but maybe it's just a figure of style I didn't understand.

In practice, I definitely do not want to use -ffast-math (if anything, I'd rather use -fexcess-precision=standard but it's, unfortunately, not available for C++). And I can't change my (well, MinGW-w64's) libm. So, again, in this particular case, if I understand you correctly, it seems like I simply will have to live with the (very small, admittedly) loss of precision after migrating the existing code base from C++98 to C++11, right?

And, to finish, yes, I did see the calculation being inlined when using -fwhole-program or -flto (although the "assembly" output of the latter is a bit more difficult to examine) and it happened even in much less trivial examples and I was duly impressed by it (no kidding). However it still doesn't (and can't) happen in my real code which is a bit more complicated.
Comment 5 Marc Glisse 2016-12-19 14:52:10 UTC
(In reply to Vadim Zeitlin from comment #4)
> Thanks for the explanation! I didn't realize the template function below was
> smart enough to select __builtin_powil() automatically,

The template selects __builtin_powl, but gcc's optimizers know that when the second argument has an integer value (in type long double), they can replace it with __builtin_powil. One might complain that it only does this transformation when the second argument is a constant, not for casts of integer variables to long double.

> thought the comment was temporary because of the question in it: "What
> should the return type of pow(float,int) be?", which seemed to require an
> answer ("double"), but maybe it's just a figure of style I didn't understand.

I think that's the title of the DR (question mark included), check the issue list to make sure.

> In practice, I definitely do not want to use -ffast-math (if anything, I'd
> rather use -fexcess-precision=standard but it's, unfortunately, not
> available for C++). And I can't change my (well, MinGW-w64's) libm. So,
> again, in this particular case, if I understand you correctly, it seems like
> I simply will have to live with the (very small, admittedly) loss of
> precision after migrating the existing code base from C++98 to C++11, right?

I think so.
Comment 6 Vadim Zeitlin 2016-12-19 15:00:25 UTC
> One might complain that it only does this transformation when the second argument is a constant, not for casts of integer variables to long double.

Yes, in the light of new information, this is what this bug is really about: due to disabling the old overloads selecting __builtin_powil() explicitly, the results are now (i.e. with C++11) different when an integer constant and an integer variable containing the same value are used, whereas they were the same before.

IMHO this is a regression, albeit a minor one, but I can understand if you decide it's not worth doing anything about it -- please just close this bug in this case.

Thanks again for your explanations!
Comment 7 Richard Biener 2016-12-20 09:45:01 UTC
Note that absence of flags such as -ffast-math only libstdc++ knows whether
the standard allows pow(, int) to be implemented with more than a single rounding.

I don't think that the middle-end converts pow(, int-valued) to powi anywhere
as generally the result can be off too far (maybe we could for some known
special values).
Comment 8 Marc Glisse 2016-12-20 10:17:24 UTC
(In reply to Richard Biener from comment #7)
> I don't think that the middle-end converts pow(, int-valued) to powi anywhere
> as generally the result can be off too far (maybe we could for some known
> special values).

For pow(x, 2.), we always get a multiplication. With -ffast-math, all pow(x, (double)int_cst) are converted to a sequence of multiplications (I just tried with pow(x,123456789) which gives a sequence of 37 multiplications at -O1 -ffast-math). But with pow(x,(double)int_var), we don't generate a call to __powidf2, even a combination of -Os -ffast-math calls __pow_finite instead.
Comment 9 Eric Gallager 2018-09-08 03:11:00 UTC
This came up on the gcc-help mailing list here: https://gcc.gnu.org/ml/gcc-help/2018-09/msg00034.html