Fix #21024 - Optimize x^^c expressions (#21082)

* Fix #21024 - Optimize x^^c expressions

Reason for the *magic* constraint c<8 on inlining x^^c:

https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/fpu/e_powl.S;h=47f129f34d368d7c67b8e5f2462b36b0bebb7621;hb=HEAD#l136

* Fix poor assumption about expression state

* Restrict optimization to floating point expressions

* Generalize optimization to any scalar data type

* Fix segfault on x^^c where x is a single member anonymous enum

DMD segfaulted on compiling the unittests in std/algorithm/sorting.o, the
unittest that caused the segfault can be reduced to:

    enum real Two = 2.0;
    auto _ = Two^^3;

I'm not sure why copying the anonymous enum into a `const` variable causes
the compiler to segfault.

* Add tests to x^^c inlining optimization

* Fix missing type for e1 ^^ -1 to 1 / e1 rewrite

* Move rewrites from constant folding to expression semantic and restrict them to [-1, 2]

* Improve error message for the x^^2 rewrite.

Before:
    ex.d(4): Error: can implicitly convert expression `(const const(double) __powtmp2 = x + 5.0;) , __powtmp2 * ...` of type `double` to `int`
        int y = ( x + 5 ) ^^ 2;
                ^
and after:
    ex.d(4): Error: cannot implicitly convert expression `(x + 5.0) ^^ 2L` of type `double` to `int`
        int y = ( x + 5 ) ^^ 2;
                ^

* Update C++ frontend header to match change in `CommaExp`

* Address code review feedback

Co-authored-by: Dennis Korpel <dkorpel@gmail.com>

---------

Co-authored-by: Dennis Korpel <dkorpel@gmail.com>
This commit is contained in:
Fares A. Bakhit 2025-03-29 13:13:38 +02:00 committed by GitHub
parent d65a100b6c
commit fa1f860e4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 151 additions and 0 deletions

View file

@ -3851,6 +3851,9 @@ extern (C++) final class CommaExp : BinExp
/// false will be passed will be from the parser.
bool allowCommaExp;
/// The original expression before any rewriting occurs.
/// This is used in error messages.
Expression originalExp;
extern (D) this(Loc loc, Expression e1, Expression e2, bool generated = true) @safe
{
@ -3858,6 +3861,12 @@ extern (C++) final class CommaExp : BinExp
allowCommaExp = isGenerated = generated;
}
extern (D) this(Loc loc, Expression e1, Expression e2, Expression oe) @safe
{
this(loc, e1, e2);
originalExp = oe;
}
override bool isLvalue()
{
return !rvalue && e2.isLvalue();

View file

@ -990,6 +990,7 @@ class CommaExp final : public BinExp
public:
d_bool isGenerated;
d_bool allowCommaExp;
Expression* originalExp;
bool isLvalue() override;
Optional<bool> toBool() override;
void accept(Visitor *v) override { v->visit(this); }

View file

@ -12796,6 +12796,58 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
return;
}
// Inline the expression, if possible.
PowExp pe = cast(PowExp)e;
if (pe.e1.type.isScalar() && pe.e2.isIntegerExp())
{
Expression one;
if (pe.e1.type.isIntegral()) {
one = new IntegerExp(e.loc, 1, pe.e1.type);
} else {
one = new RealExp(e.loc, CTFloat.one, pe.e1.type);
}
const expo = cast(sinteger_t)pe.e2.toInteger();
// Replace e1 ^^ -1 with 1 / e1
if (expo == -1)
{
Expression ex = new DivExp(exp.loc, one, pe.e1);
ex = ex.expressionSemantic(sc);
result = ex;
return;
}
// Replace e1 ^^ 0 with 1
else if (expo == 0)
{
Expression ex = one;
ex.loc = exp.loc;
ex = ex.expressionSemantic(sc);
result = ex;
return;
}
// Replace e1 ^^ 1 with e1
else if (expo == 1)
{
Expression ex = pe.e1;
ex.loc = exp.loc;
ex = ex.expressionSemantic(sc);
result = ex;
return;
}
// Replace e1 ^^ 2 with e1 * e1
else if (expo == 2)
{
auto v = copyToTemp(STC.const_, "__powtmp", pe.e1);
auto ve = new VarExp(exp.loc, v);
auto de = new DeclarationExp(exp.e1.loc, v);
auto me = new MulExp(exp.e2.loc, ve, ve);
Expression ex = new CommaExp(exp.loc, de, me, exp);
ex = ex.expressionSemantic(sc);
result = ex;
return;
}
}
Module mmath = Module.loadStdMath();
if (!mmath)
{

View file

@ -2766,6 +2766,7 @@ class CommaExp final : public BinExp
public:
const bool isGenerated;
bool allowCommaExp;
Expression* originalExp;
bool isLvalue() override;
Optional<bool > toBool() override;
void accept(Visitor* v) override;

View file

@ -2724,6 +2724,12 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt
void visitComma(CommaExp e)
{
if (e.originalExp !is null)
{
e.originalExp.expressionPrettyPrint(buf, hgs);
return;
}
// CommaExp is generated by the compiler so it shouldn't
// appear in error messages or header files.
// For now, this treats the case where the compiler

View file

@ -0,0 +1,38 @@
/*
TEST_OUTPUT:
---
fail_compilation/powinline.d(25): Error: cannot implicitly convert expression `(a + 5.0) ^^ 2L` of type `double` to `int`
fail_compilation/powinline.d(26): Error: cannot implicitly convert expression `(1.0 / foo()) ^^ 2L` of type `double` to `int`
fail_compilation/powinline.d(31): Error: void has no value
fail_compilation/powinline.d(31): Error: incompatible types for `(5.0) * (bar())`: `double` and `void`
fail_compilation/powinline.d(37): Error: cannot modify `immutable` expression `a`
---
*/
double foo()
{
return 5.0;
}
void bar()
{
return;
}
void test1()
{
double a = 2.0;
int b = (a + 5.0) ^^ 2.0;
b = (1 / foo()) ^^ 2.0;
}
void test2()
{
double a = (5.0 * bar()) ^^ 2.0;
}
void test3()
{
immutable double a = 3.0;
(a ^^= 2.0) = 6;
}

View file

@ -0,0 +1,44 @@
/*
REQUIRED_ARGS: -betterC
RUN_OUTPUT:
---
Success
---
*/
import core.stdc.stdio;
void test1()
{
enum real Two = 2.0;
static assert(Two^^3 == 8.0);
}
void test2()
{
double x = 5.0;
assert(x^^-1 == 1/x);
x = -1.0;
assert(x^^1 == x);
assert((x += 3) ^^ 2.0 == 4.0);
assert((x) ^^ 2.0 == 4.0);
assert((x *= 5) ^^ 2.0 == (x * x));
assert(x^^-1 == 1.0 / x);
assert((x^^-1) ^^ 0.0 == 1.0);
}
void test3()
{
int x = 6;
assert(x ^^ 0 == 1);
assert((x += 3) ^^ 2 == 81);
assert(x ^^ 2 == (x ^^ 1) * (x ^^ 1));
static assert(4.0 ^^ -1 == 0.25);
}
extern(C) void main()
{
test1();
test2();
test3();
printf("Success\n");
}