Skip to content

fix Issue 5227 - X ^^ FP at compile-time#8071

Merged
dlang-bot merged 1 commit intodlang:masterfrom
WalterBright:fix5227
Apr 17, 2018
Merged

fix Issue 5227 - X ^^ FP at compile-time#8071
dlang-bot merged 1 commit intodlang:masterfrom
WalterBright:fix5227

Conversation

@WalterBright
Copy link
Member

Adds the CTFE functions:

real_t nearbyint(real_t x);
real_t rint(real_t x);
real_t round(real_t x);
real_t floor(real_t x);
real_t ceil(real_t x);
real_t trunc(real_t x);
real_t log(real_t x);
real_t log2(real_t x);
real_t log10(real_t x);
real_t pow(real_t x, real_t y);
real_t expm1(real_t x);
real_t exp2(real_t x);
real_t fmin(real_t x, real_t y);
real_t fmax(real_t x, real_t y);
real_t copysign(real_t x, real_t s);
real_t fma(real_t x, real_t y, real_t z);

@dlang-bot
Copy link
Contributor

dlang-bot commented Mar 23, 2018

Thanks for your pull request, @WalterBright!

Bugzilla references

Auto-close Bugzilla Severity Description
5227 blocker X ^^ FP at compile-time

⚠️⚠️⚠️ Warnings ⚠️⚠️⚠️

To target stable perform these two steps:

  1. Rebase your branch to upstream/stable:
git rebase --onto upstream/stable upstream/master
  1. Change the base branch of your PR to stable

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub fetch digger
dub run digger -- build "master + dmd#8071"

@WalterBright WalterBright force-pushed the fix5227 branch 2 times, most recently from 6a3b4e0 to f2ea327 Compare March 23, 2018 09:18
@ibuclaw
Copy link
Member

ibuclaw commented Mar 23, 2018

Wouldn't this just add more discrepancies between compile time and runtime?

@WalterBright
Copy link
Member Author

These are only executed during CTFE, not optimized constant folding.

But certainly all the std.math functions need to be gone over again to ensure the ULPs are correct.

@kinke
Copy link
Contributor

kinke commented Mar 23, 2018

[I circumvented this problem in LDC by using the D host compiler's std.math, as we've already been depending on Phobos.]

@UplinkCoder
Copy link
Member

more builtins .... sigh.
But what does fma do there, when infact it does not produce a fused-multiply-add.

Copy link
Contributor

@TurkeyMan TurkeyMan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to see a test for the problem case... although I'm sure it works ;)

pragma(msg, "pow()");
enum powf = pow(2.5f, -3.0f); pragma(msg, powf);
enum powd = pow(2.5 , -3.0 ); pragma(msg, powd);
enum powr = pow(2.5L, -3.0L); pragma(msg, powr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no test here for the problem case. Please add a test for fractional exponent, eg: pow(2, 3.5);
That's the case that doesn't work in CTFE. Integer exponent seems to be handled as a different case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

@TurkeyMan
Copy link
Contributor

OMFG @WalterBright is my homeboy!

Yeah, even if you guys reckon this isn't the most sanitary solution, compile errors when you type 2^^1.5 has never been okay.

@WalterBright
Copy link
Member Author

But what does fma do there, when infact it does not produce a fused-multiply-add.

Neither does the std.math one, which is why I suggested a review of std.math.

I circumvented this problem in LDC by using the D host compiler's std.math

std.math may be better off in druntime. In any case, that's a separate issue. The ctfloat.d implementations are not terrible, they just rely on the C functions which aren't the best.

@ibuclaw
Copy link
Member

ibuclaw commented Mar 23, 2018

@TurkeyMan - you'll start complaining again when 2^^1.5 yields a different result at compile-time vs. run-time. ;-)

There are a couple of functions that should not be here in my opinion, I'll pick them out if they haven't been removed since I started writing.

{
Expression arg0 = (*arguments)[0];
assert(arg0.op == TOK.float64);
return new RealExp(loc, CTFloat.nearbyint(arg0.toReal()), arg0.type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes no sense, because there is no rounding mode at compile time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can 'compile time' specify a static rounding mode? Presumably 'nearest', since it's the hardware default...?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a look at my end, no, gcc emulated floating point does not.

And hardware default for who? SuperH? Motorola 6800? Soft processors on an FPGA? :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can however convert the float to mpfr and explicitly request a rounding mode.

{
Expression arg0 = (*arguments)[0];
assert(arg0.op == TOK.float64);
return new RealExp(loc, CTFloat.rint(arg0.toReal()), arg0.type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise having this as a built-in too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two are there for compatibility with LDC, and also as a simple matter of convenience so the user doesn't have to use two paths for the same code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I'd rather not add builtins that aren't common for all, as it means you will have to use two paths anyway. For example y2lx comes with version(INLINE_Y2LX).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the issue with adding them to gdc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are non existent builtins in gcc.

More generally though adding them as builtins would make them more susceptible to wrong code in the const folder/optimizer given.

setRoundingMode(down);
immutable x = rint(1.8);
setRoundingMode(up);
immutable y = rint(1.8);

@TurkeyMan
Copy link
Contributor

@ibuclaw And I'll be happy to complain when that comes up! Current situation is much worse that that hypothetical (albeit probable) moment.
None of the cases where I'm blocked on this will compare compile time results against runtime results that I'm aware of.
I need to build shit-loads of look-up tables.

@TurkeyMan
Copy link
Contributor

Incidentally; I'm used to building with fast-math and the like. I'm accustomed to CTFE not precisely matching runtime results ;)

@wilzbach
Copy link
Contributor

@TurkeyMan - you'll start complaining again when 2^^1.5 yields a different result at compile-time vs. run-time. ;-)

Yes different behavior between CTFE and runtime is horrible to debug, because once you add enum somewhere, your tests fail (or worse: they still pass, but the small difference might add up in your production program.)
For many applications that's okay and they can opt-in with fast-math, but I fully agree with @ibuclaw that this shouldn't be the default.

@WalterBright
Copy link
Member Author

Yes different behavior between CTFE and runtime is horrible to debug

There are always going to be slight differences between compile time and run time. Java early on tried to define away the difference, and they were forced to relent. If your algorithm cares about ULP, you really really need to pay close attention to how your expressions are constructed and compiled.

It isn't integer math and cannot be treated like integer math.

Anyhow, again, issues with accuracy are off topic for this PR.

@JackStouffer
Copy link
Contributor

This should get a changlog entry.


add_builtin("_D3std4math3logFNaNbNiNfeZe", &eval_log);
add_builtin("_D3std4math3logFNaNbNiNfeZe", &eval_log);
add_builtin("_D3std4math3logFNaNbNiNfeZe", &eval_log);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are adding the same function 3 times here. If the float and double overloads don't exist one builtin should be enough. Same for a number of other functions.

import std.math;

pragma(msg, "log()");
enum logf = log(5.5f); pragma(msg, logf);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come pragma(msg) is used to test this instead of static assert?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. The current tests also test that CTFE log(float|double) etc. return a real, i.e., that there are no float/double overloads, which I'm not sure is desirable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The std.math.log function does not have overloads, and passing float/double to it will return a real. I don't think CTFE should do something different than the corresponding std.math function.

Copy link
Contributor

@kinke kinke Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think CTFE should do something different than the corresponding std.math function.

That isn't the question, it's whether the float/double tests should be there if there's no corresponding function. As soon as Phobos gets proper overloads, these DMD tests will fail, so DMD has to be patched in tandem. I'm in favor of adding corresponding CTFE tests after extending Phobos.

A similar thing is blocking dlang/phobos#6272; I added those CTFE tests myself a while ago. ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the functions in Phobos first, then in dmd. Not the other way around, because they cannot be tested if put in dmd first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also recommend skipping these tests for now: Testing them here to return real will block adding the overloads to phobos.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the functions in Phobos first, then in dmd. Not the other way around, because they cannot be tested if put in dmd first.

Absolutely, but this patch here puts them in dmd first, that's what I've been trying to tell the whole time.

add_builtin("_D4core4math4fabsFNaNbNiNfeZe", &eval_fabs);
add_builtin("_D4core4math5expm1FNaNbNiNfeZe", &eval_unimp);
add_builtin("_D4core4math4exp21FNaNbNiNfeZe", &eval_unimp);
add_builtin("_D4core4math4exp2FNaNbNiNfeZe", &eval_unimp);
Copy link
Contributor

@kinke kinke Mar 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&eval_unimp => &eval_exp2 (likewise for expm1 above)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't an exp2 function in core.math. We could add to it, but that discussion is beyond the scope of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you noticed the wrong mangle & fixed it instead of just removing that useless entry?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I don't know why the eval_unimp entries are there, I didn't write that code. But I felt it was beyond the scope of this PR to address that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, the same goes for the @trusted/@safe overloads, the functions are all @safe IIRC.

add_builtin("_D4core4math4fabsFNaNbNiNeeZe", &eval_fabs);
add_builtin("_D4core4math5expm1FNaNbNiNeeZe", &eval_unimp);
add_builtin("_D4core4math4exp21FNaNbNiNeeZe", &eval_unimp);
add_builtin("_D4core4math4exp2FNaNbNiNeeZe", &eval_unimp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto too :-)

Copy link
Member

@rainers rainers Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: this replaces @safe with @trusted from the overload above. I think this overload can be safely deleted.

add_builtin("_D3std4math4fabsFNaNbNiNfeZe", &eval_fabs);
add_builtin("_D3std4math5expm1FNaNbNiNfeZe", &eval_unimp);
add_builtin("_D3std4math4exp21FNaNbNiNfeZe", &eval_unimp);
add_builtin("_D3std4math4exp2FNaNbNiNfeZe", &eval_unimp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto tree

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, not ditto. :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std.math has annotated exp2 with @trusted, so this overload can go in favor of the one a few lines below.

@RazvanN7
Copy link
Contributor

Tests are all green. ping @ibuclaw

@WalterBright
Copy link
Member Author

@ibuclaw please respond

@rainers
Copy link
Member

rainers commented Apr 15, 2018

I agree with Ian that rint and nearbyint don't make much sense without also supporting fesetround or similar. You can always use floor, ceil, trunc or round as explicit replacements.

Please also avoid testing the return type of fallbacks for non-existing functions, these block adding them to core/std.math. You could replace them with static asserts checking the value, but not the type.

@WalterBright
Copy link
Member Author

@rainers ok, done. Except I didn't do the static assert, it seemed pointless.

assert(arg0.op == TOK.float64);
Expression arg1 = (*arguments)[1];
assert(arg1.op == TOK.float64);
return new RealExp(loc, CTFloat.copysign(arg0.toReal(), cast(int) arg1.toInteger()), arg0.type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LDC fails to comple this line (no implicit conversion to real_t). Why do you cast to int here? copysign expects a real parameter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rainers - Doesn't ldc implement their own builtins module independent of dmd?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but as noted, this is a usage of struct longdouble introduced by compilation with LDC. AFAICT passing int here is wrong anyway, should just be arg1.toReal().

@rainers
Copy link
Member

rainers commented Apr 15, 2018

Apart from the failure LGTM

@ibuclaw
Copy link
Member

ibuclaw commented Apr 16, 2018

I guess the build failure is for platforms where dmd uses struct longdouble. Check and use explicit casts to real_t

@WalterBright
Copy link
Member Author

@rainers I'm shooting in the dark here with appveyor. Now I'm getting the error:

..\dmd\root\ctfloat.d(88): error : cannot cast expression `roundl(cast(real)x.opCast())` of type `real` to `longdouble`

What do you advise I do to make this work?

@rainers
Copy link
Member

rainers commented Apr 16, 2018

It needs a small part of #8169:

diff --git a/src/dmd/root/longdouble.d b/src/dmd/root/longdouble.d
index 4b3cedc..0ce7acd 100644
--- a/src/dmd/root/longdouble.d
+++ b/src/dmd/root/longdouble.d
@@ -45,6 +45,13 @@ nothrow:
         else
             ld_set(&this, d);
     }
+    this(real r)
+    {
+        static if (real.sizeof > 8)
+            *cast(real*)&this = r;
+        else
+            this(cast(double)r);
+    }
 
     void opAssign(float f) { ld_set(&this, f); }
     void opAssign(double f) { ld_set(&this, f); }
@@ -71,6 +78,11 @@ nothrow:
         else static if(is(T == double)) return cast(T)ld_read(&this);
         else static if(is(T == long))   return ld_readll(&this);
         else static if(is(T == ulong))  return ld_readull(&this);
+        else static if(is(T == real))
+        {
+            static if (real.sizeof > 8) return *cast(real*)&this;
+            else                        return cast(T)ld_read(&this);
+        }
         else static assert(false, "usupported type");
     }

Then this works:

        static real_t round(real_t x) { return real_t(core.stdc.math.roundl(cast(real)x)); }
        static real_t floor(real_t x) { return real_t(core.stdc.math.floor(cast(real)x)); }
        static real_t ceil(real_t x) { return real_t(core.stdc.math.ceil(cast(real)x)); }
        static real_t trunc(real_t x) { return real_t(core.stdc.math.trunc(cast(real)x)); }
        static real_t log(real_t x) { return real_t(core.stdc.math.logl(cast(real)x)); }
        static real_t log2(real_t x) { return real_t(core.stdc.math.log2l(cast(real)x)); }
        static real_t log10(real_t x) { return real_t(core.stdc.math.log10l(cast(real)x)); }
        static real_t pow(real_t x, real_t y) { return real_t(core.stdc.math.powl(cast(real)x, cast(real)y)); }
        static real_t expm1(real_t x) { return real_t(core.stdc.math.expm1l(cast(real)x)); }
        static real_t exp2(real_t x) { return real_t(core.stdc.math.exp2l(cast(real)x)); }
        static real_t copysign(real_t x, real_t s) { return real_t(core.stdc.math.copysignl(cast(real)x, cast(real)s)); }

and could even be used for the part is(real_t == real).

@WalterBright WalterBright force-pushed the fix5227 branch 2 times, most recently from abf9651 to f1f46b5 Compare April 16, 2018 19:28
@WalterBright
Copy link
Member Author

@rainers sigh, now I get this error with appveyor:

lib\druntime32mscoff.lib: Error: MS-Coff object module `errno_c_32mscoff.obj` has magic = 5ad5, should be 8664
lib\druntime32mscoff.lib: Error: MS-Coff object module `msvc_32mscoff.obj` has magic = 5ad5, should be 8664
lib\druntime32mscoff.lib: Error: MS-Coff object module `msvc_math_32mscoff.obj` has magic = 5ad5, should be 8664

@rainers
Copy link
Member

rainers commented Apr 16, 2018

Sorry, atm I have no idea why the object files start with 0x5ad5. Locally the files have magic 0x14c (the message is still for x64 only) and building works fine.

Maybe just another spurious failure? You can restart the build by amending the same commit again.

@MartinNowak
Copy link
Member

You can restart the build by amending the same commit again.

git commit --allow-empty --amend is handy for that purpose, basically just updates the commit date, make sure you haven't staged anything though.

@MartinNowak
Copy link
Member

Thanks for fixing this @WalterBright, missing log/exp support in CTFE has been a long-standing annoyance, in particular for precomputing scientific tables.

@TurkeyMan
Copy link
Contributor

Thanks @WalterBright! This is the most exciting patch in years! :P
Looking forward to the next release.

@WalterBright
Copy link
Member Author

My pleasure!

@WalterBright WalterBright deleted the fix5227 branch April 18, 2018 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.