www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Simple partial evaluation

To compile code efficiently the compiler has to find/infer lot of semantics
from the code, and then needs ways to use such information.

From what I've seen using such information (for example to split or merge
loops, to tile loop iterations on arrays, to inline virtual functions, to
inline delegates, to unroll recursive templates, and so on) is not the harder
thing. Quite harder is finding such semantics in the first place.

The compiler can infer some of such semantics studying for example the
structure of two nested loops, the inheritance tree, the virtual call points,
performing run-time profiling of the code in many different ways, iterating on
the variables and constants to perform type inference, finding where
delegates/lambdas come from to inline them, and so on. This can be sometimes
done, but requires a complex compiler, and the compiler risks getting slow too
(caching on disk some of such semantics may help a lot, but not the first time
some code is compiled).

To reduce the compiler complexity, to increase safety a little, and speed up
compilation, languages offer ways to tag and annotate types and other things,
so attributes/keywords like const, immutable, pure, inline, restrict, etc. etc.
are added.

For the programmer adding such annotations requires time and brain, so their
number is better kept low. A significant percentage of lines of code of most
programs isn't speed-critical, so adding many annotations everywhere may look
like a waste of time (and looking for speed in such code can produce longer
code, unsafe code, and less readable code, all in situations where speed is not
important).

So a possible solution to this looks like having optional annotations, this is
done for example in CLisp. One problem of this is that some annotations may
work only if used consistently everywhere. Another problem for the programmer
is how to find where to put such annotations. CLisp solves this problem
printing to the user where it's not able to optimize things away (it prints
such things only relative to functions the programmer has annotated as "fast",
to avoid flooding the programmer with too many warnings).

Time ago I have asked if D may run functions at compile-time, when possible,
even if their result isn't assigned to a constant. Related to this there is the
more general idea of partial compilation. In D a limited form of partial
compilation can be done manually using templated functions: some arguments can
be template arguments, so a good compiler in theory can specialize the code for
them. But using such templated functions as normal functions (where all
arguments are known at run time) is not easy/possible, this may force to
duplicate code (using a (string) mixins to avoid code duplication is sometimes
possible, but the result may not look good). 

Few old nice papers about partial evaluation:
"Partial Evaluation Applied to Ray Tracing" (1995) by Peter Holst Andersen:
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.31.5469
"C-Mix: Making Easily Maintainable C-Programs run FAST":
http://www.diku.dk/~panic/articles/STTT98-cmix.ps.gz

Implementing a general automatic partial compilation in D may look like too
much work, but there can be ways to add optional annotations to functions to
allow a partial compilation locally that for this purpose works better than D
templated functions, gain speed, keep the both the compiler simple enough, keep
most of the source code short, and avoid excessive bloat in the compiled binary.

C-Mix uses the "residual" directive to tell the partial specializator that a
variable has to kept as variable and not specialized (often to avoid code
bloat).

In several things D prefers to give the programmer tools to do things, instead
of letting the compiler do all by itself (but inlining is left to the compiler
in DMD. LDC has two pragmas that give back to the programmer the possibility to
inline asm functions and force inlining).

So D may offer an annotation that does the opposite of residual, that tells the
compiler a certain variable can be specialized. A subset of this feature is to
allow the programmer to know when a certain argument given to a function is
known at compile time.

So something like this can allow the compiler to produce two compiled versions
of this function, one where x is a compile-time constant and one where it's
not. With this the compiler doesn't need to be smart, and the programmer can
control the amount of code bloat produced.

void foo(int x, int y) {
    static if (__traits(isCTconstant, x)) {
        ...
    } else {
        ...
}

This is just an half-backed idea, but I think it may be improved.

Bye,
bearophile
Sep 11 2009