Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Begin porting implementation from tiny_simgrid
authorMaxwell Pirtle <maxwellpirtle@gmail.com>
Mon, 6 Feb 2023 14:30:44 +0000 (15:30 +0100)
committerMaxwell Pirtle <maxwellpirtle@gmail.com>
Mon, 20 Feb 2023 09:43:52 +0000 (10:43 +0100)
More of the structure of the current implementation
of the UDPOR algorithm was imported into SimGrid
with minor alterations to better fit the EventSet
class paradigm, along with some other cleaning up.

The next phase will be two-fold:

1. Importantly, state management will need to be
added into SimGrid. This shouldn't be too difficult,
assuming that we can follow a similar strategy as
The Pham had in tiny_simgrid of mapping State
objects to state id's and using those ids to later
manipulate the state. In SimGrid, the state will be
be used to manipulate the remote process to which
the Exploration has access, instead of manipulating
what's effectively a dummy object

2. Implementing most of the TODO functions
in EventSet etc. This functionality will come
in future commits.

Eventually, we'll want to make an iterative version
of UDPOR to prevent stack overflows, as it is likely
that the unfoldings of programs of any significant size
can get quite deep. For now, though, this is not
strictly necessary to get a first draft implemented

src/mc/explo/UdporChecker.cpp
src/mc/explo/UdporChecker.hpp
src/mc/udpor_global.hpp

index 812a2e6..d649460 100644 (file)
@@ -10,9 +10,149 @@ XBT_LOG_NEW_DEFAULT_SUBCATEGORY(mc_udpor, mc, "Logging specific to MC safety ver
 
 namespace simgrid::mc {
 
-UdporChecker::UdporChecker(const std::vector<char*>& args) : Exploration(args) {}
+UdporChecker::UdporChecker(const std::vector<char*>& args) : Exploration(args)
+{
+  /* Create initial data structures, if any ...*/
+  XBT_INFO("Starting a UDPOR exploration");
+
+  // TODO: Initialize state structures for the search
+}
+
+void UdporChecker::run()
+{
+  // NOTE: `A`, `D`, and `C` are derived from the
+  // original UDPOR paper [1], while
+  // `prev_exC` arises from the incremental computation
+  // of ex(C) from the former paper described in [3]
+  EventSet A, D;
+  Configuration C;
+  EventSet prev_exC;
+
+  /**
+   * Maintains the mapping between handles referenced by events in
+   * the current state of the unfolding
+   */
+  StateManager state_manager_;
+
+  const auto initial_state    = std::make_unique<State>(get_remote_app());
+  const auto initial_state_id = state_manager_.record_state(std::move(initial_state));
+  const auto root_event       = std::make_unique<UnfoldingEvent>(-1, "", EventSet(), initial_state_id);
+  explore(std::move(C), {EventSet()}, std::move(D), std::move(A), root_event.get(), std::move(prev_exC));
+}
+
+void UdporChecker::explore(Configuration C, std::list<EventSet> max_evt_history, EventSet D, EventSet A,
+                           UnfoldingEvent* cur_evt, EventSet prev_exC)
+{
+  // Perform the incremental computation of exC
+  auto [exC, enC] = this->extend(C, max_evt_history, *cur_evt, prev_exC);
+
+  // If enC is a subset of D, intuitively
+  // there aren't any enabled transitions
+  // which are "worth" exploring since their
+  // exploration would lead to a so-called
+  // "sleep-set blocked" trace.
+  if (enC.is_subset_of(D)) {
+
+    // Log traces
+    if (C.events_.size() > 0) {
+
+      // g_var::nb_traces++;
+
+      // TODO: Log here correctly
+      // XBT_DEBUG("\n Exploring executions: %d : \n", g_var::nb_traces);
+      // ...
+      // ...
+    }
+
+    // When `en(C)` is empty, intuitively this means that there
+    // are no enabled transitions that can be executed from the
+    // state reached by `C` (denoted `state(C)`) (i.e. by some
+    // execution of the transitions in C obeying the causality
+    // relation). Hence, it is at this point we should check for a deadlock
+    if (enC.empty()) {
+      get_remote_app().check_deadlock();
+    }
+
+    return;
+  }
+
+  UnfoldingEvent* e = this->select_next_unfolding_event(A, enC);
+  if (e == nullptr) {
+    XBT_ERROR("\n\n****** CRITICAL ERROR ****** \n"
+              "UDPOR guarantees that an event will be chosen here, yet no events were actually chosen...\n"
+              "******************");
+    DIE_IMPOSSIBLE;
+  }
+
+  // TODO: Add verbose logging about which event is being explored
+
+  // TODO: Execute the transition associated with the event
+  // and map the new state
+
+  // const auto cur_state_id = cur_evt->get_state_id();
+  // auto curEv_StateId = currentEvt->get_state_id();
+  // auto nextState_id  = App::app_side_->execute_transition(curEv_StateId, e->get_transition_tag());
+  // e->set_state_id(nextState_id);
+
+  // Configuration is the same + event e
+  // C1 = C + {e}
+  Configuration C1 = C;
+  C1.events_.insert(e);
+  C1.updateMaxEvent(e);
 
-void UdporChecker::run() {}
+  max_evt_history.push_back(C1.maxEvent);
+
+  // A <-- A \ {e}, ex(C) <-- ex(C) \ {e}
+  A.remove(e);
+  exC.remove(e);
+
+  // Explore(C + {e}, D, A \ {e})
+  this->explore(C1, max_evt_history, D, A, e, exC);
+
+  // D <-- D + {e}
+  D.insert(e);
+
+  // TODO: Determine a value of K to use or don't use it at all
+  constexpr unsigned K = 10;
+  auto J               = this->compute_partial_alternative(D, C, K);
+  if (!J.empty()) {
+    J.subtract(C.events_);
+    max_evt_history.pop_back();
+    explore(C, max_evt_history, D, J, cur_evt, prev_exC);
+  }
+
+  // D <-- D - {e}
+  D.remove(e);
+  this->clean_up_explore(e, C, D);
+}
+
+std::tuple<EventSet, EventSet> UdporChecker::extend(const Configuration& C, const std::list<EventSet>& max_evt_history,
+                                                    const UnfoldingEvent& cur_event, const EventSet& prev_exC) const
+{
+  // exC.remove(cur_event);
+
+  // TODO: Compute extend() as it exists in tiny_simgrid
+
+  // exC.subtract(C);
+  return std::tuple<EventSet, EventSet>();
+}
+
+UnfoldingEvent* UdporChecker::select_next_unfolding_event(const EventSet& A, const EventSet& enC)
+{
+  // TODO: Actually select an event here
+  return nullptr;
+}
+
+EventSet UdporChecker::compute_partial_alternative(const EventSet& D, const EventSet& C, const unsigned k) const
+{
+  // TODO: Compute k-partial alternatives using [2]
+  return EventSet();
+}
+
+void UdporChecker::clean_up_explore(const UnfoldingEvent* e, const EventSet& C, const EventSet& D)
+{
+  // TODO: Perform clean up here
+}
 
 RecordTrace UdporChecker::get_record_trace()
 {
index 76469f9..fec1146 100644 (file)
 #include "src/mc/mc_record.hpp"
 #include "src/mc/udpor_global.hpp"
 
+#include <optional>
+
 namespace simgrid::mc {
 
+/**
+ * @brief Performs exploration of a concurrent system via the
+ * UDPOR algorithm
+ *
+ * The `UdporChecker` implementation is based primarily off three papers,
+ * herein referred to as [1], [2], and [3] respectively, as well as the
+ * current implementation of `tiny_simgrid`:
+ *
+ * 1. "Unfolding-based Partial Order Reduction" by Rodriguez et al.
+ * 2. Quasi-Optimal Partial Order Reduction by Nguyen et al.
+ * 3. The Anh Pham's Thesis "Exploration efficace de l'espace ..."
+ */
 class XBT_PRIVATE UdporChecker : public Exploration {
 public:
   explicit UdporChecker(const std::vector<char*>& args);
@@ -29,7 +43,7 @@ private:
   uint32_t nb_traces = 0;
 
   /**
-   * The "relevant" portions of the unfolding that must be kept around to ensure that
+   * @brief The "relevant" portions of the unfolding that must be kept around to ensure that
    * UDPOR properly searches the state space
    *
    * The set `U` is a global variable which is maintained by UDPOR
@@ -38,9 +52,20 @@ private:
    *
    * @invariant: When a new event is created by UDPOR, it is inserted into
    * this set. All new events that are created by UDPOR have causes that
-   * also exist in U and are valid for the duration of the search
+   * also exist in U and are valid for the duration of the search.
+   *
+   * If an event is discarded instead of moved from set `U` to set `G`,
+   * the event and its contents will be discarded.
    */
   EventSet U;
+
+  /**
+   * @brief The "irrelevant" portions of the unfolding that do not need to be kept
+   * around to ensure that UDPOR functions correctly
+   *
+   * The set `G` is another global variable maintained by the UDPOR algorithm which
+   * is used to keep track of all events which used to be important to UDPOR
+   */
   EventSet G;
 
 private:
@@ -51,8 +76,57 @@ private:
    *
    * TODO: Explain what this means here
    */
-  void explore(Configuration C, std::list<EventSet> maxEvtHistory, EventSet D, EventSet A, UnfoldingEvent* currentEvt,
+  void explore(Configuration C, std::list<EventSet> max_evt_history, EventSet D, EventSet A, UnfoldingEvent* cur_event,
                EventSet prev_exC);
+
+  /**
+   * @brief Computes the sets `ex(C)` and `en(C)` of the given configuration
+   * `C` as an incremental computation from the the previous computation of `ex(C)`
+   *
+   * A central component to UDPOR is the computation of the set `ex(C)`. The
+   * extension set `ex(C)` of a configuration `C` is defined as the set of events
+   * outside of `C` whose full dependency chain is contained in `C` (see [1]
+   * for more details).
+   *
+   * In general, computing `ex(C)` is very expensive. In paper [3], The Anh Pham
+   * shows a method of incremental computation of the set `ex(C)` under the
+   * conclusions afforded under the computation model in consideration, of which
+   * SimGrid is apart, which allow for `ex(C)` to be computed much more efficiently.
+   * Intuitively, the idea is to take advantage of the fact that you can avoid a lot
+   * of repeated computation by exploiting the aforementioned properties (in [3]) in
+   * what is effectively a dynamic programming optimization. See [3] for more details
+   *
+   * @param C the configuration based on which the two sets `ex(C)` and `en(C)` are
+   * computed
+   * @param cur_event the event where UDPOR currently "rests", viz. the event UDPOR
+   * is now currently considering
+   * @param prev_exC the previous value of `ex(C)`, viz. that which was computed for
+   * the configuration `C' := C - {cur_event}`
+   * @returns a tuple containing the pair of sets `ex(C)` and `en(C)` respectively
+   */
+  std::tuple<EventSet, EventSet> extend(const Configuration& C, const std::list<EventSet>& max_evt_history,
+                                        const UnfoldingEvent& cur_event, const EventSet& prev_exC) const;
+
+  /**
+   * @brief Identifies the next event from the unfolding of the concurrent system
+   * that should next be explored as an extension of a configuration with
+   * enabled events `enC`
+   *
+   * @param A The set of events `A` maintained by the UDPOR algorithm to help
+   * determine how events should be selected. See the original paper [1] for more details
+   *
+   * @param enC The set `enC` of enabled events from the extension set `exC` used
+   * by the UDPOR algorithm to select new events to search. See the original
+   * paper [1] for more details
+   */
+  UnfoldingEvent* select_next_unfolding_event(const EventSet& A, const EventSet& enC);
+
+  EventSet compute_partial_alternative(const EventSet& D, const EventSet& C, const unsigned k) const;
+
+  /**
+   *
+   */
+  void clean_up_explore(const UnfoldingEvent* e, const EventSet& C, const EventSet& D);
 };
 } // namespace simgrid::mc
 
index 050c8ff..ac4a781 100644 (file)
@@ -31,7 +31,7 @@ public:
   EventSet(EventSet&&)                 = default;
   EventSet(const Configuration&);
 
-  void remove(UnfoldingEvent* e);
+  void remove(const UnfoldingEvent* e);
   void subtract(const EventSet& other);
   void subtract(const Configuration& other);
   EventSet subtracting(const EventSet& e) const;
@@ -45,6 +45,7 @@ public:
   EventSet make_union(const EventSet&) const;
   EventSet make_union(const Configuration& e) const;
 
+  size_t size() const;
   bool empty() const;
   bool contains(const UnfoldingEvent* e) const;
   bool is_subset_of(const EventSet& other) const;
@@ -131,7 +132,7 @@ private:
   std::map<Handle, std::unique_ptr<State>> state_map_;
 
 public:
-  Handle record_state(std::unique_ptr<State>&&);
+  Handle record_state(const std::unique_ptr<State>&&);
 };
 
 } // namespace simgrid::mc