Add a step that recognizes if two subtrees of a union node are
topologically equivalent and merges them. This corresponds to removing
duplicate fields in unions.
This deduplication was prevously done while emitting layouts.
A check is inserted into DLAMakeLayouts to assert that, after
constructing unions, no union has only one child, which could be the
case if we didn't deduplicate union fields in the graph.
These are the elementary steps of the DataLayoutAnalysis (that is still
to be defined), along with a manager to handle them, their dependencies,
their executions and their invalidations.
In principle, this design could have been implemented inheriting from
`llvm::Pass` and `llvm::legacy::PassManager` (or `llvm::PassInfoMixin`
and `llvm::PMDataManager` for the new step manager).
However, LLVM's passes and managers provide far more complex features
than we need for now, so I decided to keep it simple and roll our own
for now.
We could potentially switch to LLVM stuff later if we need their power.
Some examples of features we don't need for now are:
- capability to pass results from a pass to another that depends on it
- capability to dynamically compute pass dependencies
- capability to dynamically invalidate passes
If some of these become necessary in future, it's a sign that it might
be time to switch to an LLVM-based design
The design is structured as follows.
1) Adding `Step`s to the `StepManager`
The `StepManager` owns the `Step`s, that can be added via the `addStep`
method.
Whenever a new `Step` is added, four properties are checked:
- that all its dependencies have already been added before it;
- that non of its dependencies have been invalidated by the insertion of
another step that invalidate them.
- that it does not depend on itself
- that it does not invalidate itself
If any of these conditions fail, the `Step` is not added successfully.
Whenever a new `Step` A is added and it invalidates any `Step` B, the
last instance of B added to the `StepManager` before A (if present) is
marked as invalidated.
If one wants to add a new `Step` C that depends on B, it will be
necessary to explicitly add a new instance of A (say A*) before adding
C.
Otherwise, there will be no guarantee that an instance of A executes
before C without being invalidated.
These conditions on invalidations are restrictive but the allow to
specify the dependencies and invalidations that we need for now.
If it turns out we need more complex dependencies and invalidations we
should explore implementing the LLVM-based design.
2) Executing the added `Step`s
After adding the `Step`s to the `StepManger`, they can be executed all
together with the `dla::StepManger::run()` method.
This method runs all the steps in the order they were added by the user
with calls to `addStep`.
The fact that `addStep` strictly ensures that dependencies and
invalidations are respected, guarantees that the execution always works
in the order of `Step`s specified by the user.
It also means that there is no need to check and propagate invalidations
across dependencies between the execution of the `Step`s, because there
is the guarantee that if a `Step` A invalidates `Step` B, another
instance of B will be executed later if some `Step` C needs it, or if
the user has added it explicitly.
When the `dla::StepManager::run()` method returns, all the `Step`s that
were successfully added to the manager have finished running.
3) Implementing a `Step`
Each `Step` has a virtual `runOnTypeSystem` method, that executes the
`Step` and returns true if some change was made, false otherwise.
`TypeSystem` for now is just a forward declaration and all the
implementations of `runOnTypeSystem` simply return true.
This commit does three things.
- Removes explicit dependency from boost version 1.63. This is no longer
necessary since orchestra has moved to compiling boost test directly,
and ships version 1.71 (as of now).
- Switches UnitTests.cmake to using modern cmake package for
Boost::unit_test_framework
- Adds an header copied from revng, to define
boost::throw_exception(std::exception const &E)
This is necessary to compile with -fno-exception and boost
unit_test_framework.
The method which computes the weight for a `BasicBlockNode` now relies
on a new `WeightTraits` which is specialized for `BasicBlock` and for
`DotNode`.
In this way, we can change the implementation of the `getWeight` method
without having to modify the implementation inside the `revng` project.
`BasicBlockNode` and `RegionCFG` classes are now template classes. This
means that the `BasicBlockNode` class can be used as a generic wrapper
for any type of object in the original graph (it is usually used to wrap
a `llvm::BasicBlock *` for decompilation purposes, but in tests it can
be used to wrap a `DotNode` object) that implementes `GraphTraits`.