Transitive Intermodule Optimization
Christopher Rodd SPEIRS
crs at students.cs.mu.oz.au
Mon Nov 17 18:54:30 AEDT 1997
Hi All,
When I committed termination analysis, it included code to
handle transitive intermodule optimization. Unfortunatly, the code is
not finished. As it stands, the code correctly creates the .trans_opt
file and correctly reads it back. However, the code does not guarantee
correctness, as circular dependencies can be created. (for details, see
the beginning of trans_opt.m). There are a number of possible solutions
to the problems, but none of them are perfect, so I thought I would
explain the possibilities so we can discuss which is the best solution.
Firstly, reasons to have transitive intermodule optimization.
The main benefit that it provides is to allow more accurate information
about imported modules to be used when compiling. Its use is not
limited to termination analysis, for example it would be very useful
with inlining, where predicates could be inlined transitively. For
termination analysis, the accuracy of the information is particularly
important. To prove the termination of a predicate, we must be able to
prove the termination of every predicate that that predicate depends on,
transitively.
Possible Solutions:
Using a .order file.
The idea behind this solution is to create a <module>.order file
when mmake <module>.depend is run. This file would store a
topological ordering of the modules in the program. When compiling
a given module, x, which imports module y, y.trans_opt could only be
read if y is lower in the ordering than x. This guarantees that no
circular dependencies can occur. Note that the information does not
necessarily need to reside in a file, it could be stored in the
mmakefile, and passed to mmc through an environment variable.
Problems:
- The information in the .order file would only be updated when
mmake <module>.depend is run, which is not very often.
- When circular dependencies exist between modules (x imports y,
and y imports x), it would be difficult to break these
circularities in an optimal way. (currently our best solution is
to impose alphabetical ordering to break cycles). In particular,
when x imports y for types only, then it would be preferable if y
appeared above x in the ordering.
- When there are 2 seperate programs which both use some common
modules, there are problems ensuring correctness. Suppose program
A and program B both use modules x, y and z. When mmake A.depend
is run, z depends on y, y depends on x, so the ordering is z,y,x.
when B.depend is run, an additional dependency of x depends on z
has been added, so the ordering is x,y,z. This problem would
need to be considered when deciding upon a solution.
.opt2 .opt3 ... files.
This solution would store transitive intermodule information in a
series of files, such that each file could only depend on files
which come earlier in the series. (so, m.opt3 could depend on
m.opt2, but not the other way round).
Problems:
- Too many files would be created, and too many compilations
would be required. Perhaps it would be possible to store all the
information in one file, maintaining the dependency internally,
but this introduces other difficulties.
- For a line of n dependencies (A depends on B depends on C ...), n
opt files would be required.
Track and update dependencies locally, ensuring that circularities
cannot develop.
This would be implemented by storing the names of all .trans_opt
files that any particular .trans_opt file depends on in the file.
Other .trans opt files may then depend on that file if it does not
introduce a circularity. For example, X.trans_opt depends on
Y.trans_opt and Z.trans_opt. When making Z.trans_opt, X.trans_opt
could not be used, as this would create a circularity.
Problems:
- Parallel makes. With simultaneous compilations, it is difficult
to ensure that circularities cannot develop. Perhaps it can be
avoided:
- Store actual dependencies in .d files. Therefore, the
dependencies are properly handled by make, and files are
recreated when they are out of date.
- Store `would like to depend on' in .trans_opt files.
- Algorithm is:
- Suppose that when creating A.trans_opt, we would like to use
the information in B.trans_opt.
- If B.trans_opt does not list a dependency on A.trans_opt and
B.d does not list a dependency of B.trans_opt on A.trans_opt
then add a 'would like to depend on' to A.trans_opt, but the
information in B.trans_opt cannot be read yet.
- The next time A.trans_opt is created, if there is still no
dependency of B.trans_opt on A.trans_opt, then add
B.trans_opt to A.d. Question: Can B.trans_opt be read at
this stage, or do we need to wait till the next compilation?
If there is a dependency, then remove the 'would like to
depend on' if the file you want to depend on is higher (in
alphabetical ordering) than the current file. This ensures
that the dependency is removed from one file.
- It takes quite a few recompilations to get each dependency
correct.
- It is unclear how to set up the dependencies initially, but using
the ordering produced for the .order file would seem logical.
- Do we need to do anything special when removing dependencies, or
can we just delete the dependencies from the .d and .trans_opt
files?
Also, it is possible to use the .opt2 .opt3 solution in conjunction with
either the first or last solution. Such a solution would not need many
files, I believe that in practice 3 files would be sufficient. Note
that the .opt2 file would only be useful when there is a circularity in
the dependencies.
Chris
More information about the developers
mailing list