Files
revng-revng/tests/Unit/DLAStepManager.cpp
T
Pietro Fezzardi 52b617a6dd [DLA] Add dla::Step and dla::StepManager
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.
2021-02-02 11:23:53 +01:00

235 lines
6.4 KiB
C++

/// \file DLAStepManager.cpp
/// \brief Tests for dla::StepManager
//
// Copyright (c) rev.ng Srls. See LICENSE.md for details.
//
#define BOOST_TEST_MODULE DLAStepManager
bool init_unit_test();
#include "boost/test/unit_test.hpp"
#include "llvm/ADT/SmallPtrSet.h"
#include "revng/Support/Assert.h"
#include "revng/UnitTestHelpers/UnitTestHelpers.h"
#include "lib/Decompiler/DLAStep.h"
namespace dla {
class SelfDependentStep : public Step {
static const char ID;
public:
static const constexpr void *getID() { return &ID; }
SelfDependentStep() : Step(ID, { SelfDependentStep::getID() }, {}) {}
virtual ~SelfDependentStep() override = default;
virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override { return false; }
};
const char SelfDependentStep::ID = 0;
class SelfInvalidatingStep : public Step {
static const char ID;
public:
static const constexpr void *getID() { return &ID; }
SelfInvalidatingStep() : Step(ID, {}, { SelfInvalidatingStep::getID() }) {}
virtual ~SelfInvalidatingStep() override = default;
virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override { return false; }
};
const char SelfInvalidatingStep::ID = 0;
class StepWithNoDeps : public Step {
static const char ID;
public:
static const constexpr void *getID() { return &ID; }
StepWithNoDeps() : Step(ID) {}
virtual ~StepWithNoDeps() override = default;
virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override { return false; }
};
const char StepWithNoDeps::ID = 0;
class SingleDependencyStep : public Step {
static const char ID;
public:
static const constexpr void *getID() { return &ID; }
SingleDependencyStep() : Step(ID, { StepWithNoDeps::getID() }, {}) {}
virtual ~SingleDependencyStep() override = default;
virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override { return false; }
};
const char SingleDependencyStep::ID = 0;
class StepInvalidateNoDeps : public Step {
static const char ID;
public:
static const constexpr void *getID() { return &ID; }
StepInvalidateNoDeps() : Step(ID, {}, { StepWithNoDeps::getID() }) {}
virtual ~StepInvalidateNoDeps() override = default;
virtual bool runOnTypeSystem(LayoutTypeSystem &TS) override { return false; }
};
const char StepInvalidateNoDeps::ID = 0;
} // end namespace dla
using namespace dla;
BOOST_AUTO_TEST_CASE(InitiallyEmpty) {
StepManager SM;
BOOST_TEST(SM.getNumSteps() == 0);
BOOST_TEST(SM.hasValidSchedule());
SM.reset();
BOOST_TEST(SM.getNumSteps() == 0);
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertSelfDependency) {
StepManager SM;
BOOST_TEST(not SM.addStep(std::unique_ptr<Step>(new SelfDependentStep())));
BOOST_TEST(SM.getNumSteps() == 0);
// The schedule is still valid because the bad step is not inserted
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertSelfInvalidation) {
StepManager SM;
BOOST_TEST(not SM.addStep(std::unique_ptr<Step>(new SelfInvalidatingStep())));
BOOST_TEST(SM.getNumSteps() == 0);
// The schedule is still valid because the bad step is not inserted
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertStepWithNoDeps) {
StepManager SM;
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new StepWithNoDeps())));
BOOST_TEST(SM.getNumSteps() == 1);
BOOST_TEST(SM.hasValidSchedule());
SM.reset();
BOOST_TEST(SM.getNumSteps() == 0);
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertSameStepTwice) {
StepManager SM;
BOOST_TEST(SM.addStep(std::make_unique<StepWithNoDeps>()));
BOOST_TEST(SM.getNumSteps() == 1);
BOOST_TEST(SM.hasValidSchedule());
BOOST_TEST(SM.addStep(std::make_unique<StepWithNoDeps>()));
BOOST_TEST(SM.getNumSteps() == 2);
BOOST_TEST(SM.hasValidSchedule());
SM.reset();
BOOST_TEST(SM.getNumSteps() == 0);
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertWithValidDependency) {
StepManager SM;
BOOST_TEST(SM.addStep(std::make_unique<StepWithNoDeps>()));
BOOST_TEST(SM.getNumSteps() == 1);
BOOST_TEST(SM.hasValidSchedule());
BOOST_TEST(SM.addStep(std::make_unique<SingleDependencyStep>()));
BOOST_TEST(SM.getNumSteps() == 2);
BOOST_TEST(SM.hasValidSchedule());
SM.reset();
BOOST_TEST(SM.getNumSteps() == 0);
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(InsertWithMissingDependency) {
StepManager SM;
BOOST_TEST(not SM.addStep(std::make_unique<SingleDependencyStep>()));
BOOST_TEST(SM.getNumSteps() == 0);
// This must be true becase the step is not inserted
BOOST_TEST(SM.hasValidSchedule());
}
BOOST_AUTO_TEST_CASE(SimpleInvalidate) {
StepManager SM;
// Initially the sched is empty
BOOST_TEST(SM.sched_begin() == SM.sched_end());
// Inserting a step with no dependencies should always succeed
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new StepWithNoDeps())));
BOOST_TEST(SM.getNumSteps() == 1);
BOOST_TEST(SM.hasValidSchedule());
// Inserting a step with a valid dependency that is already scheduled should
// succeed and leave SM with a valid schedule
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new SingleDependencyStep())));
BOOST_TEST(SM.getNumSteps() == 2);
BOOST_TEST(SM.hasValidSchedule());
// Insert a step with no dependencies and that invalidates StepWithNoDeps.
// This should succeed, while invalidating StepWithNoDeps.
// If later no other step is added that depends on StepWithNoDeps, the
// schedule should still be valid.
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new StepInvalidateNoDeps())));
BOOST_TEST(SM.getNumSteps() == 3);
BOOST_TEST(SM.hasValidSchedule());
// If we now add a step that depends on the invalidated step StepWithNoDeps,
// the insertion should fail.
BOOST_TEST(not SM.addStep(std::unique_ptr<Step>(new SingleDependencyStep())));
BOOST_TEST(SM.getNumSteps() == 3);
// Again the schedule remains in a valid state, because the bad step is not
// added.
BOOST_TEST(SM.hasValidSchedule());
// We then add a new istance of the invalidated StepWithNoDeps, that is
// succesfully appended to the end of the schedule.
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new StepWithNoDeps())));
BOOST_TEST(SM.getNumSteps() == 4);
BOOST_TEST(SM.hasValidSchedule());
// Now the second instance of SingleDependencyStep can be added
// successfully because the second instance of StepWithNoDeps is scheduled
// before it and no other step invalidates it.
BOOST_TEST(SM.addStep(std::unique_ptr<Step>(new SingleDependencyStep())));
BOOST_TEST(SM.getNumSteps() == 5);
BOOST_TEST(SM.hasValidSchedule());
}