Don wrote:
Daniel Keep wrote:


Andrei Alexandrescu wrote:
[snip]

All right, let's do this with operator overloading :o). Ideas? I have a couple, but I don't want to introduce bias.

Andrei

The only idea I can come up with is to introduce the following:

struct Vector
{
    macro opExpression(ast)
    {
        ...
    }
}

The compile-time opExpression macro's job is to spit out a function that takes each leaf of the AST as an argument and returns the final value of the expression.

You could simplify it by ensuring that you don't get any subexpressions that don't result in the type "Vector". For example:

Vector a, b, c;
double g, h;

a = b + (g*h) * c;

The AST would have "(g*h)" as a single node of type double.

Of course, this would probably be exceedingly complex, both on the compiler and user side of things. I can't imagine what the contents of that macro would even look like...

My BLADE library pretty much does that, so you don't need to guess.
The ast is a string of the form "A=B+(C*D)", together with a type tuple Tuple!(Vector, Vector, double, Vector), and a values array ["a","b","(g*h)","c"].
("g*h" is something like "4.564e+2" if g and h are compile-time constants).
One approach would be to look at that code and try to simplify it.

It performs the following steps. Note that it uses [5...3, 6, , 2..$]
or [5...3, 6, 0..$, 2..$] syntax for multi-dimensional slicing.

=== (1) Simplify the expression. ===
(A) Remove all duplicate symbols.
(B) Combine all scalars into a single scalar. Combine slicing operations into vector variables.
(C) Deal with slices.
- A[B..C, whatever][D..E] = A[(B+D)..(B+E), whatever] for any non-slicable expression A, including something ending with an index.
         Assert(E-D <= C-B).
      (Special case A[B..$, whatever][D..$] = A[(B+D)..$, whatever])
    - A[whatever][D..E] = A[D..E, whatever] if A is slicable.
(D) Rank and arithmetic transformations.
    - Use slicing distributive law for linear algebra:
      Given A[B..C] for expressions A,B,C
      where B and C are non-slicable, and A is slicable, the slice
      can be moved to every vector inside A. Note that this may
      convert matrix multiplications into dot products.
- Remove unary minus where possible, eg A-(-B) => A+B, abs(-A) => abs(A).
    - Use associativity of * in intrinsics:
      sum(A*V) => A*sum(V), abs(A*B) => abs(A)*abs(B)
(E) Expression standardisation
- Move multiplies to left: Convert A[]*B into B*A[] (assumes * is commutative, not valid for quaternions).
    - Convert C-A*B into C+(-A)*B whenever possible.
(F) Remove '$'. Convert x[$] into x[x.length].
(G) Check all of the ranks for each operation. Add each one to the list of asserts.

=== (2) Categorize the expression ===
This determines which type of loop it is. For example, with a matrix-vector multiply you have nested loops.

=== (3) Generate asserts ===
Spit out all the asserts which were generated during the simplification pass.

=== (4) Generate code, based on expression category ===
I only did this for a few cases, but it's generally pretty straightforward. It would get much more complicated once you start adding cache blocking techniques and sparse matrices...

I completely implemented steps 1 to 3, so this isn't complete fantasy.



The one positive to this method is that it's about as general as you can get; assuming this was implemented, it would hopefully be possible to implement a simpler scheme on top of it.

struct Vector
{
    mixin FusionOverloadFor!("+","-","*","/",".dot",".cross");
}

I really hope there's a better way.

  -- Daniel

Reply via email to