Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'master' of https://framagit.org/simgrid/simgrid
authormlaurent <mathieu.laurent@ens-rennes.fr>
Tue, 13 Jun 2023 09:17:57 +0000 (11:17 +0200)
committermlaurent <mathieu.laurent@ens-rennes.fr>
Tue, 13 Jun 2023 09:17:57 +0000 (11:17 +0200)
27 files changed:
MANIFEST.in
docs/source/Configuring_SimGrid.rst
docs/source/Models.rst
examples/cpp/CMakeLists.txt
src/deprecated.cpp
src/kernel/EngineImpl.hpp
src/kernel/resource/CpuImpl.cpp
src/kernel/resource/DiskImpl.cpp
src/kernel/resource/Resource.hpp
src/kernel/resource/StandardLinkImpl.cpp
src/kernel/resource/models/cpu_cas01.cpp
src/kernel/resource/models/cpu_ti.cpp
src/kernel/xml/sg_platf.cpp
src/mc/api/ActorState.hpp
src/mc/explo/UdporChecker.cpp
src/mc/explo/udpor/EventSet.cpp
src/mc/explo/udpor/EventSet.hpp
src/mc/explo/udpor/ExtensionSetCalculator.cpp
src/mc/explo/udpor/ExtensionSetCalculator.hpp
src/mc/explo/udpor/ExtensionSet_test.cpp [new file with mode: 0644]
src/mc/explo/udpor/UnfoldingEvent.cpp
src/mc/explo/udpor/UnfoldingEvent.hpp
src/mc/sosp/PageStore.cpp
src/mc/transition/TransitionComm.cpp
src/mc/transition/TransitionComm.hpp
src/mc/transition/TransitionSynchro.hpp
tools/cmake/Tests.cmake

index 0caa0f9..296c687 100644 (file)
@@ -2228,6 +2228,7 @@ include src/mc/explo/udpor/Configuration_test.cpp
 include src/mc/explo/udpor/EventSet.cpp
 include src/mc/explo/udpor/EventSet.hpp
 include src/mc/explo/udpor/EventSet_test.cpp
+include src/mc/explo/udpor/ExtensionSet_test.cpp
 include src/mc/explo/udpor/ExtensionSetCalculator.cpp
 include src/mc/explo/udpor/ExtensionSetCalculator.hpp
 include src/mc/explo/udpor/History.cpp
index 45a12bc..a908b1e 100644 (file)
@@ -1329,12 +1329,9 @@ existing MPI libraries. The ``smpi/coll-selector`` item can be used to
 select the decision logic either of the OpenMPI or the MPICH libraries. (By
 default SMPI uses naive version of collective operations.)
 
-Each collective operation can be manually selected with a
-``smpi/collective_name:algo_name``. Available algorithms are listed in
-:ref:`SMPI_use_colls`.
-
-.. TODO:: All available collective algorithms will be made available
-          via the ``smpirun --help-coll`` command.
+Each collective operation can be manually selected with a ``smpi/collective_name:algo_name``. For example, if you want to use
+the Bruck algorithm for the Alltoall algorithm, you should use ``--cfg=smpi/alltoall:bruck`` on the command-line of smpirun. The
+reference of all available algorithms are listed in :ref:`SMPI_use_colls`.
 
 .. _cfg=smpi/barrier-collectives:
 
index 1688d80..229d61c 100644 (file)
@@ -182,15 +182,16 @@ latency-factor is 13.01, bandwidth-factor is 0.97 while weight-S is 20537. Lets
      <link_ctn id="link1" />
    </route>
 
-If host `A` sends ``100kB`` (a hundred kilobytes) to host `B`, one can expect that this communication would take `0.81`
-seconds to complete according to a simple latency-plus-size-divided-by-bandwidth model (0.01 + 8e5/1e6 = 0.81) since the
-latency is small enough to ensure that the physical bandwidth is used (see the discussion on CM02 above). However, the
-LV08 model is more complex to account for three phenomena that directly impact the simulation time:
+If host `A` sends ``100kB`` (a hundred kilobytes, that is, 8e5 bits) to host `B`, one can expect that this communication would
+take `0.81` seconds to complete according to a simple latency-plus-size-divided-by-bandwidth model (0.01 + 8e5/1e6 = 0.81 -- the
+size was converted from bytes to bits) since the latency is small enough to ensure that the physical bandwidth is used (see the
+discussion on CM02 above). However, the LV08 model is more complex to account for three phenomena that directly impact the
+simulation time:
 
   - The size of a message at the application level (i.e., 100kB in this example) is not the size that is actually
     transferred over the network. To mimic the fact that TCP and IP headers are added to each packet of the original
     payload, the TCP model of SimGrid empirically considers that `only 97% of the nominal bandwidth` are available. In
-    other words, the size of your message is increased by a few percents, whatever this size be.
+    other words, the size of your message is increased by a few percents, whichever this size.
 
   - In the real world, the TCP protocol is not able to fully exploit the bandwidth of a link from the emission of the
     first packet. To reflect this `slow start` phenomenon, the latency declared in the platform file is multiplied by
index 709bebe..dc66fc9 100644 (file)
@@ -66,10 +66,13 @@ if(SIMGRID_HAVE_STATEFUL_MC)
     # This example never ends, disable it for now
     set(_mc-bugged2-liveness_disable 1)
 
-    ADD_TESH(s4u-mc-bugged1-liveness-visited-ucontext --setenv bindir=${CMAKE_CURRENT_BINARY_DIR}/mc-bugged1-liveness
-                                                      --setenv platfdir=${CMAKE_HOME_DIRECTORY}/examples/platforms
-                                                      --cd ${CMAKE_CURRENT_SOURCE_DIR}/mc-bugged1-liveness
-                                                       ${CMAKE_HOME_DIRECTORY}/examples/cpp/mc-bugged1-liveness/s4u-mc-bugged1-liveness-visited.tesh)
+    if ("${CMAKE_SYSTEM}" MATCHES "Linux")
+      # timeout under FreeBSD (test never stops)
+      ADD_TESH(s4u-mc-bugged1-liveness-visited-ucontext --setenv bindir=${CMAKE_CURRENT_BINARY_DIR}/mc-bugged1-liveness
+                                                        --setenv platfdir=${CMAKE_HOME_DIRECTORY}/examples/platforms
+                                                        --cd ${CMAKE_CURRENT_SOURCE_DIR}/mc-bugged1-liveness
+                                                        ${CMAKE_HOME_DIRECTORY}/examples/cpp/mc-bugged1-liveness/s4u-mc-bugged1-liveness-visited.tesh)
+    endif()
     IF(HAVE_C_STACK_CLEANER)
       add_dependencies(tests-mc s4u-mc-bugged1-liveness-stack-cleaner)
       # This test checks if the stack cleaner is making a difference:
index 79b6a89..0881f54 100644 (file)
@@ -16,6 +16,8 @@
 #define SIMIX_H_NO_DEPRECATED_WARNING // avoid deprecation warning on include (remove with XBT_ATTRIB_DEPRECATED_v335)
 #include <simgrid/simix.h>
 
+#include <cmath>
+
 void simcall_comm_send(simgrid::kernel::actor::ActorImpl* sender, simgrid::kernel::activity::MailboxImpl* mbox,
                        double task_size, double rate, void* src_buff, size_t src_buff_size,
                        bool (*match_fun)(void*, void*, simgrid::kernel::activity::CommImpl*),
index 33a12dd..4c25e54 100644 (file)
@@ -19,7 +19,6 @@
 #include "src/kernel/activity/SleepImpl.hpp"
 #include "src/kernel/activity/Synchro.hpp"
 #include "src/kernel/actor/ActorImpl.hpp"
-#include "src/kernel/resource/SplitDuplexLinkImpl.hpp"
 
 #include <boost/intrusive/list.hpp>
 #include <map>
index 313b3fc..acd7aa4 100644 (file)
@@ -4,7 +4,6 @@
  * under the terms of the license (GNU LGPL) which comes with this package. */
 
 #include "src/kernel/resource/CpuImpl.hpp"
-#include "src/kernel/EngineImpl.hpp"
 #include "src/kernel/resource/models/cpu_ti.hpp"
 #include "src/kernel/resource/profile/Profile.hpp"
 #include "src/simgrid/math_utils.h"
@@ -157,16 +156,7 @@ void CpuImpl::turn_off()
 {
   if (is_on()) {
     Resource::turn_off();
-
-    const kernel::lmm::Element* elem = nullptr;
-    double now                       = EngineImpl::get_clock();
-    while (const auto* var = get_constraint()->get_variable(&elem)) {
-      Action* action = var->get_id();
-      if (action->get_state() == Action::State::INITED || action->get_state() == Action::State::STARTED) {
-        action->set_finish_time(now);
-        action->set_state(Action::State::FAILED);
-      }
-    }
+    cancel_actions();
   }
 }
 
index a231432..20c23ac 100644 (file)
@@ -71,16 +71,7 @@ void DiskImpl::turn_off()
     Resource::turn_off();
     s4u::Disk::on_onoff(piface_);
     piface_.on_this_onoff(piface_);
-
-    const kernel::lmm::Element* elem = nullptr;
-    double now                       = EngineImpl::get_clock();
-    while (const auto* var = get_constraint()->get_variable(&elem)) {
-      Action* action = var->get_id();
-      if (action->get_state() == Action::State::INITED || action->get_state() == Action::State::STARTED) {
-        action->set_finish_time(now);
-        action->set_state(Action::State::FAILED);
-      }
-    }
+    cancel_actions();
   }
 }
 
index 9e2f284..1ed1e35 100644 (file)
@@ -7,6 +7,7 @@
 #define SIMGRID_KERNEL_RESOURCE_RESOURCE_HPP
 
 #include "simgrid/forward.h"
+#include "src/kernel/EngineImpl.hpp"
 #include "src/kernel/actor/Simcall.hpp"
 #include "src/kernel/lmm/maxmin.hpp" // Constraint
 #include "src/kernel/resource/profile/Event.hpp"
@@ -72,6 +73,21 @@ template <class AnyResource> class Resource_T : public Resource {
   Model* model_                = nullptr;
   lmm::Constraint* constraint_ = nullptr;
 
+protected:
+  void cancel_actions()
+  {
+    const kernel::lmm::Element* elem = nullptr;
+    double now                       = EngineImpl::get_clock();
+    while (const auto* var = get_constraint()->get_variable(&elem)) {
+      Action* action = var->get_id();
+      if (action->get_state() == Action::State::INITED || action->get_state() == Action::State::STARTED ||
+          action->get_state() == Action::State::IGNORED) {
+        action->set_finish_time(now);
+        action->set_state(Action::State::FAILED);
+      }
+    }
+  }
+
 public:
   using Resource::Resource;
   /** @brief setup the profile file with states events (ON or OFF). The profile must contain boolean values. */
index a7661a1..9df40d0 100644 (file)
@@ -5,7 +5,6 @@
 
 #include <simgrid/s4u/Engine.hpp>
 
-#include "src/kernel/EngineImpl.hpp"
 #include "src/kernel/resource/StandardLinkImpl.hpp"
 #include <numeric>
 
@@ -93,16 +92,7 @@ void StandardLinkImpl::turn_off()
     Resource::turn_off();
     s4u::Link::on_onoff(piface_);
     piface_.on_this_onoff(piface_);
-
-    const kernel::lmm::Element* elem = nullptr;
-    double now                       = EngineImpl::get_clock();
-    while (const auto* var = get_constraint()->get_variable(&elem)) {
-      Action* action = var->get_id();
-      if (action->get_state() == Action::State::INITED || action->get_state() == Action::State::STARTED) {
-        action->set_finish_time(now);
-        action->set_state(Action::State::FAILED);
-      }
-    }
+    cancel_actions();
   }
 }
 
index c938624..35af6fa 100644 (file)
@@ -113,20 +113,8 @@ void CpuCas01::apply_event(profile::Event* event, double value)
         get_iface()->turn_on();
       }
     } else {
-      const lmm::Element* elem = nullptr;
-      double date              = EngineImpl::get_clock();
-
       get_iface()->turn_off();
-
-      while (const auto* var = get_constraint()->get_variable(&elem)) {
-        Action* action = var->get_id();
-
-        if (action->get_state() == Action::State::INITED || action->get_state() == Action::State::STARTED ||
-            action->get_state() == Action::State::IGNORED) {
-          action->set_finish_time(date);
-          action->set_state(Action::State::FAILED);
-        }
-      }
+      cancel_actions();
     }
     unref_state_event();
 
index c927bec..6b522bb 100644 (file)
@@ -387,13 +387,13 @@ void CpuTi::apply_event(kernel::profile::Event* event, double value)
       }
     } else {
       get_iface()->turn_off();
-      double date = EngineImpl::get_clock();
 
       /* put all action running on cpu to failed */
+      double now = EngineImpl::get_clock();
       for (CpuTiAction& action : action_set_) {
         if (action.get_state() == Action::State::INITED || action.get_state() == Action::State::STARTED ||
             action.get_state() == Action::State::IGNORED) {
-          action.set_finish_time(date);
+          action.set_finish_time(now);
           action.set_state(Action::State::FAILED);
           get_model()->get_action_heap().remove(&action);
         }
index 0d64feb..b4b4a32 100644 (file)
@@ -23,6 +23,7 @@
 #include "src/kernel/EngineImpl.hpp"
 #include "src/kernel/resource/DiskImpl.hpp"
 #include "src/kernel/resource/HostImpl.hpp"
+#include "src/kernel/resource/StandardLinkImpl.hpp"
 #include "src/kernel/resource/profile/Profile.hpp"
 #include "src/kernel/xml/platf.hpp"
 #include "src/kernel/xml/platf_private.hpp"
index 2e8e136..ed40454 100644 (file)
@@ -164,7 +164,8 @@ public:
 
   const std::vector<std::shared_ptr<Transition>>& get_enabled_transitions() const
   {
-    return this->pending_transitions_;
+    static const auto no_enabled_transitions = std::vector<std::shared_ptr<Transition>>();
+    return this->is_enabled() ? this->pending_transitions_ : no_enabled_transitions;
   };
 };
 
index 6bd87a6..43c229f 100644 (file)
@@ -64,7 +64,12 @@ void UdporChecker::explore(const Configuration& C, EventSet D, EventSet A, Event
     // possibility is that we've finished running everything, and
     // we wouldn't be in deadlock then)
     if (enC.empty()) {
-      XBT_VERB("Maximal configuration detected. Checking for deadlock...");
+      XBT_VERB("**************************");
+      XBT_VERB("*** TRACE INVESTIGATED ***");
+      XBT_VERB("**************************");
+      XBT_VERB("Execution sequence:");
+      for (auto const& s : get_textual_trace())
+        XBT_VERB("  %s", s.c_str());
       get_remote_app().check_deadlock();
     }
 
@@ -109,8 +114,8 @@ void UdporChecker::explore(const Configuration& C, EventSet D, EventSet A, Event
     // that we are searching again from `state(C)`. While the
     // stack of states is properly adjusted to represent
     // `state(C)` all together, the RemoteApp is currently sitting
-    // at some *future* state with resepct to `state(C)` since the
-    // recursive calls have moved it there.
+    // at some *future* state with respect to `state(C)` since the
+    // recursive calls had moved it there.
     restore_program_state_with_current_stack();
 
     // Explore(C, D + {e}, J \ C)
@@ -150,11 +155,22 @@ EventSet UdporChecker::compute_exC(const Configuration& C, const State& stateC,
   EventSet exC                = prev_exC;
   exC.remove(e_cur);
 
+  // IMPORTANT NOTE: In order to have deterministic results, we need to process
+  // the actors in a deterministic manner so that events are discovered by
+  // UDPOR in a deterministic order. The processing done here always processes
+  // actors in a consistent order since `std::map` is by-default ordered using
+  // `std::less<Key>` (see the return type of `State::get_actors_list()`)
   for (const auto& [aid, actor_state] : stateC.get_actors_list()) {
-    for (const auto& transition : actor_state.get_enabled_transitions()) {
-      XBT_DEBUG("\t Considering partial extension for %s", transition->to_string().c_str());
-      EventSet extension = ExtensionSetCalculator::partially_extend(C, &unfolding, transition);
-      exC.form_union(extension);
+    const auto& enabled_transitions = actor_state.get_enabled_transitions();
+    if (enabled_transitions.empty()) {
+      XBT_DEBUG("\t Actor `%ld` is disabled: no partial extensions need to be considered", aid);
+    } else {
+      XBT_DEBUG("\t Actor `%ld` is enabled", aid);
+      for (const auto& transition : enabled_transitions) {
+        XBT_DEBUG("\t Considering partial extension for %s", transition->to_string().c_str());
+        EventSet extension = ExtensionSetCalculator::partially_extend(C, &unfolding, transition);
+        exC.form_union(extension);
+      }
     }
   }
   return exC;
@@ -236,16 +252,23 @@ UnfoldingEvent* UdporChecker::select_next_unfolding_event(const EventSet& A, con
                                 "empty before attempting to select an event from it?");
   }
 
+  // UDPOR's exploration is non-deterministic (as is DPOR's)
+  // in the sense that at any given point there may
+  // be multiple paths that can be followed. The correctness and optimality
+  // of the algorithm remains unaffected by the route taken by UDPOR when
+  // given multiple choices; but to ensure that SimGrid itself has deterministic
+  // behavior on all platforms, we always pick events with lower id's
+  // to ensure we explore the unfolding deterministically.
   if (A.empty()) {
-    return const_cast<UnfoldingEvent*>(*(enC.begin()));
-  }
-
-  for (const auto& event : A) {
-    if (enC.contains(event)) {
-      return const_cast<UnfoldingEvent*>(event);
-    }
+    const auto min_event = std::min_element(enC.begin(), enC.end(),
+                                            [](const auto e1, const auto e2) { return e1->get_id() < e2->get_id(); });
+    return const_cast<UnfoldingEvent*>(*min_event);
+  } else {
+    const auto intersection = A.make_intersection(enC);
+    const auto min_event    = std::min_element(intersection.begin(), intersection.end(),
+                                               [](const auto e1, const auto e2) { return e1->get_id() < e2->get_id(); });
+    return const_cast<UnfoldingEvent*>(*min_event);
   }
-  return nullptr;
 }
 
 void UdporChecker::clean_up_explore(const UnfoldingEvent* e, const Configuration& C, const EventSet& D)
index b644448..feef1dd 100644 (file)
@@ -90,6 +90,19 @@ EventSet EventSet::make_union(const Configuration& config) const
   return make_union(config.get_events());
 }
 
+EventSet EventSet::make_intersection(const EventSet& other) const
+{
+  std::unordered_set<const UnfoldingEvent*> result;
+
+  for (const UnfoldingEvent* e : other.events_) {
+    if (contains(e)) {
+      result.insert(e);
+    }
+  }
+
+  return EventSet(std::move(result));
+}
+
 EventSet EventSet::get_local_config() const
 {
   return History(*this).get_all_events();
@@ -110,6 +123,11 @@ bool EventSet::contains(const UnfoldingEvent* e) const
   return this->events_.find(e) != this->events_.end();
 }
 
+bool EventSet::contains_equivalent_to(const UnfoldingEvent* e) const
+{
+  return std::find_if(begin(), end(), [=](const UnfoldingEvent* e_in_set) { return *e == *e_in_set; }) != end();
+}
+
 bool EventSet::is_subset_of(const EventSet& other) const
 {
   // If there is some element not contained in `other`, then
index ddd875d..9c09185 100644 (file)
@@ -28,18 +28,9 @@ public:
   EventSet& operator=(EventSet&&)      = default;
   EventSet(EventSet&&)                 = default;
   explicit EventSet(Configuration&& config);
-  explicit EventSet(std::vector<const UnfoldingEvent*>&& raw_events) : events_(raw_events.begin(), raw_events.end())
-  {
-    xbt_assert(std::none_of(events_.begin(), events_.end(), [](const auto* e) { return e == nullptr; }), "Oh no");
-  }
-  explicit EventSet(std::unordered_set<const UnfoldingEvent*>&& raw_events) : events_(raw_events)
-  {
-    xbt_assert(std::none_of(events_.begin(), events_.end(), [](const auto* e) { return e == nullptr; }), "Oh no");
-  }
-  explicit EventSet(std::initializer_list<const UnfoldingEvent*> event_list) : events_(std::move(event_list))
-  {
-    xbt_assert(std::none_of(events_.begin(), events_.end(), [](const auto* e) { return e == nullptr; }), "Oh no");
-  }
+  explicit EventSet(std::vector<const UnfoldingEvent*>&& raw_events) : events_(raw_events.begin(), raw_events.end()) {}
+  explicit EventSet(std::unordered_set<const UnfoldingEvent*>&& raw_events) : events_(raw_events) {}
+  explicit EventSet(std::initializer_list<const UnfoldingEvent*> event_list) : events_(std::move(event_list)) {}
 
   auto begin() const { return this->events_.begin(); }
   auto end() const { return this->events_.end(); }
@@ -59,12 +50,15 @@ public:
   EventSet make_union(const UnfoldingEvent*) const;
   EventSet make_union(const EventSet&) const;
   EventSet make_union(const Configuration&) const;
+  EventSet make_intersection(const EventSet&) const;
   EventSet get_local_config() const;
 
   size_t size() const;
   bool empty() const;
+
   bool contains(const UnfoldingEvent*) const;
   bool contains(const History&) const;
+  bool contains_equivalent_to(const UnfoldingEvent*) const;
   bool intersects(const EventSet&) const;
   bool intersects(const History&) const;
   bool is_subset_of(const EventSet&) const;
index b50a454..d9d8e26 100644 (file)
@@ -27,20 +27,24 @@ EventSet ExtensionSetCalculator::partially_extend(const Configuration& C, Unfold
   const static HandlerMap handlers =
       HandlerMap{{Action::COMM_ASYNC_RECV, &ExtensionSetCalculator::partially_extend_CommRecv},
                  {Action::COMM_ASYNC_SEND, &ExtensionSetCalculator::partially_extend_CommSend},
-                 {Action::COMM_WAIT, &ExtensionSetCalculator::partially_extend_CommWait}};
+                 {Action::COMM_WAIT, &ExtensionSetCalculator::partially_extend_CommWait},
+                 {Action::COMM_TEST, &ExtensionSetCalculator::partially_extend_CommTest},
+                 {Action::MUTEX_ASYNC_LOCK, &ExtensionSetCalculator::partially_extend_MutexAsyncLock},
+                 {Action::MUTEX_UNLOCK, &ExtensionSetCalculator::partially_extend_MutexUnlock},
+                 {Action::MUTEX_WAIT, &ExtensionSetCalculator::partially_extend_MutexWait},
+                 {Action::MUTEX_TEST, &ExtensionSetCalculator::partially_extend_MutexTest},
+                 {Action::ACTOR_JOIN, &ExtensionSetCalculator::partially_extend_ActorJoin}};
 
   if (const auto handler = handlers.find(action->type_); handler != handlers.end()) {
     return handler->second(C, U, std::move(action));
   } else {
-    xbt_assert(false,
-               "There is currently no specialized computation for the transition "
-               "'%s' for computing extension sets in UDPOR, so the model checker cannot "
-               "determine how to proceed. Please submit a bug report requesting "
-               "that the transition be supported in SimGrid using UDPOR and consider "
-               "using the other model-checking algorithms supported by SimGrid instead "
-               "in the meantime",
-               action->to_string().c_str());
-    DIE_IMPOSSIBLE;
+    xbt_die("There is currently no specialized computation for the transition "
+            "'%s' for computing extension sets in UDPOR, so the model checker cannot "
+            "determine how to proceed. Please submit a bug report requesting "
+            "that the transition be supported in SimGrid using UDPOR and consider "
+            "using the other model-checking algorithms supported by SimGrid instead "
+            "in the meantime",
+            action->to_string().c_str());
   }
 }
 
@@ -65,8 +69,8 @@ EventSet ExtensionSetCalculator::partially_extend_CommSend(const Configuration&
   }
 
   // 2. foreach e ∈ C s.t. λ(e) ∈ {AsyncSend(m, _), TestAny(Com)} where
-  // Com contains a matching c' = AsyncReceive(m, _) with a
-  for (const auto* e : C) {
+  // Com contains a matching c' = AsyncReceive(m, _) with `action`
+  for (const auto e : C) {
     const bool transition_type_check = [&]() {
       if (const auto* async_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
           async_send != nullptr) {
@@ -79,11 +83,9 @@ EventSet ExtensionSetCalculator::partially_extend_CommSend(const Configuration&
     if (transition_type_check) {
       const EventSet K = EventSet({e, pre_event_a_C.value_or(e)}).get_largest_maximal_subset();
 
-      // TODO: Check D_K(a, lambda(e))
-      if (true) {
-        const auto* e_prime = U->discover_event(std::move(K), send_action);
-        exC.insert(e_prime);
-      }
+      // TODO: Check D_K(a, lambda(e)) (only matters in the case of CommTest)
+      const auto e_prime = U->discover_event(std::move(K), send_action);
+      exC.insert(e_prime);
     }
   }
 
@@ -96,8 +98,6 @@ EventSet ExtensionSetCalculator::partially_extend_CommRecv(const Configuration&
 {
   EventSet exC;
 
-  // TODO: if this is the first action by the actor, no such previous event exists.
-  // How do we react here? Do we say we're dependent with the root event?
   const auto recv_action      = std::static_pointer_cast<CommRecvTransition>(std::move(action));
   const unsigned recv_mailbox = recv_action->get_mailbox();
   const auto pre_event_a_C    = C.pre_event(recv_action->aid_);
@@ -113,7 +113,7 @@ EventSet ExtensionSetCalculator::partially_extend_CommRecv(const Configuration&
 
   // 2. foreach e ∈ C s.t. λ(e) ∈ {AsyncSend(m, _), TestAny(Com)} where
   // Com contains a matching c' = AsyncReceive(m, _) with a
-  for (const auto* e : C) {
+  for (const auto e : C) {
     const bool transition_type_check = [&]() {
       if (const auto* async_recv = dynamic_cast<const CommRecvTransition*>(e->get_transition());
           async_recv != nullptr && async_recv->get_mailbox() == recv_mailbox) {
@@ -126,7 +126,7 @@ EventSet ExtensionSetCalculator::partially_extend_CommRecv(const Configuration&
     if (transition_type_check) {
       const EventSet K = EventSet({e, pre_event_a_C.value_or(e)}).get_largest_maximal_subset();
 
-      // TODO: Check D_K(a, lambda(e))
+      // TODO: Check D_K(a, lambda(e)) (ony matters in the case of TestAny)
       if (true) {
         const auto* e_prime = U->discover_event(std::move(K), recv_action);
         exC.insert(e_prime);
@@ -166,7 +166,7 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
   });
   xbt_assert(issuer != C.end(),
              "Invariant violation! A (supposedly) enabled `CommWait` transition "
-             "waiting on commiunication %lu should not be enabled: the receive/send "
+             "waiting on communication %lu should not be enabled: the receive/send "
              "transition which generated the communication is not an action taken "
              "to reach state(C) (the state of the configuration), which should "
              "be an impossibility if `%s` is enabled. Please report this as "
@@ -251,20 +251,19 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
         }
 
       } else {
-        xbt_assert(false,
-                   "The transition which created the communication on which `%s` waits "
-                   "is neither an async send nor an async receive. The current UDPOR "
-                   "implementation does not know how to check if `CommWait` is enabled in "
-                   "this case. Was a new transition added?",
-                   e_issuer->get_transition()->to_string().c_str());
+        xbt_die("The transition which created the communication on which `%s` waits "
+                "is neither an async send nor an async receive. The current UDPOR "
+                "implementation does not know how to check if `CommWait` is enabled in "
+                "this case. Was a new transition added?",
+                e_issuer->get_transition()->to_string().c_str());
       }
     }
   }
 
   // 3. foreach event e in C do
-  for (const auto* e : C) {
-    if (const CommSendTransition* e_issuer_send = dynamic_cast<const CommSendTransition*>(e_issuer->get_transition());
-        e_issuer_send != nullptr) {
+  if (const CommSendTransition* e_issuer_send = dynamic_cast<const CommSendTransition*>(e_issuer->get_transition());
+      e_issuer_send != nullptr) {
+    for (const auto e : C) {
       // If the provider of the communication for `CommWait` is a
       // `CommSend(m)`, then we only care about `e` if `λ(e) == `CommRecv(m)`.
       // All other actions would be independent with the wait action (including
@@ -290,8 +289,6 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
         continue;
       }
 
-      // TODO: Compute the send and receive positions
-
       // What send # is the issuer
       const unsigned send_position = std::count_if(e_issuer_history.begin(), e_issuer_history.end(), [=](const auto e) {
         const CommSendTransition* e_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
@@ -313,10 +310,11 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
       if (send_position == receive_position) {
         exC.insert(U->discover_event(std::move(K), wait_action));
       }
-
-    } else if (const CommRecvTransition* e_issuer_recv =
-                   dynamic_cast<const CommRecvTransition*>(e_issuer->get_transition());
-               e_issuer_recv != nullptr) {
+    }
+  } else if (const CommRecvTransition* e_issuer_recv =
+                 dynamic_cast<const CommRecvTransition*>(e_issuer->get_transition());
+             e_issuer_recv != nullptr) {
+    for (const auto e : C) {
       // If the provider of the communication for `CommWait` is a
       // `CommRecv(m)`, then we only care about `e` if `λ(e) == `CommSend(m)`.
       // All other actions would be independent with the wait action (including
@@ -365,15 +363,336 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
         exC.insert(U->discover_event(std::move(K), wait_action));
       }
     }
+  } else {
+    xbt_die("The transition which created the communication on which `%s` waits "
+            "is neither an async send nor an async receive. The current UDPOR "
+            "implementation does not know how to check if `CommWait` is enabled in "
+            "this case. Was a new transition added?",
+            e_issuer->get_transition()->to_string().c_str());
   }
 
   return exC;
 }
 
 EventSet ExtensionSetCalculator::partially_extend_CommTest(const Configuration& C, Unfolding* U,
-                                                           std::shared_ptr<Transition> test_action)
+                                                           std::shared_ptr<Transition> action)
 {
-  return EventSet();
+  EventSet exC;
+
+  const auto test_action   = std::static_pointer_cast<CommTestTransition>(std::move(action));
+  const auto test_comm     = test_action->get_comm();
+  const auto test_aid      = test_action->aid_;
+  const auto pre_event_a_C = C.pre_event(test_action->aid_);
+
+  // Add the previous event as a dependency (if it's there)
+  if (pre_event_a_C.has_value()) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), test_action);
+    exC.insert(e_prime);
+  }
+
+  // Determine the _issuer_ of the communication of the `CommTest` event
+  // in `C`. The issuer of the `CommTest` in `C` is the event in `C`
+  // whose transition is the `CommRecv` or `CommSend` whose resulting
+  // communication this `CommTest` tests on
+  const auto issuer = std::find_if(C.begin(), C.end(), [=](const UnfoldingEvent* e) {
+    if (const CommRecvTransition* e_issuer_receive = dynamic_cast<const CommRecvTransition*>(e->get_transition());
+        e_issuer_receive != nullptr) {
+      return e_issuer_receive->aid_ == test_aid && test_comm == e_issuer_receive->get_comm();
+    }
+
+    if (const CommSendTransition* e_issuer_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
+        e_issuer_send != nullptr) {
+      return e_issuer_send->aid_ == test_aid && test_comm == e_issuer_send->get_comm();
+    }
+
+    return false;
+  });
+  xbt_assert(issuer != C.end(),
+             "An enabled `CommTest` transition (%s) is testing a communication"
+             "%lu not created by a receive/send "
+             "transition. SimGrid cannot currently handle test actions "
+             "under which a test is performed on a communication that was "
+             "not directly created by a receive/send operation of the same actor.",
+             test_action->to_string(false).c_str(), test_action->get_comm());
+  const UnfoldingEvent* e_issuer = *issuer;
+  const History e_issuer_history(e_issuer);
+
+  // 3. foreach event e in C do
+  if (const CommSendTransition* e_issuer_send = dynamic_cast<const CommSendTransition*>(e_issuer->get_transition());
+      e_issuer_send != nullptr) {
+    for (const auto e : C) {
+      // If the provider of the communication for `CommTest` is a
+      // `CommSend(m)`, then we only care about `e` if `λ(e) == `CommRecv(m)`.
+      // All other actions would be independent with the test action (including
+      // another `CommSend` to the same mailbox: `CommTest` is testing the
+      // corresponding receive action)
+      if (e->get_transition()->type_ != Transition::Type::COMM_ASYNC_RECV) {
+        continue;
+      }
+
+      const auto issuer_mailbox = e_issuer_send->get_mailbox();
+
+      if (const CommRecvTransition* e_recv = dynamic_cast<const CommRecvTransition*>(e->get_transition());
+          e_recv->get_mailbox() != issuer_mailbox) {
+        continue;
+      }
+
+      // If the `issuer` is not in `config(K)`, this implies that
+      // `CommTest()` is always disabled in `config(K)`; hence, it
+      // is independent of any transition in `config(K)` (according
+      // to formal definition of independence)
+      const EventSet K    = EventSet({e, pre_event_a_C.value_or(e)});
+      const auto config_K = History(K);
+      if (not config_K.contains(e_issuer)) {
+        continue;
+      }
+
+      // What send # is the issuer
+      const unsigned send_position = std::count_if(e_issuer_history.begin(), e_issuer_history.end(), [=](const auto e) {
+        const CommSendTransition* e_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
+        if (e_send != nullptr) {
+          return e_send->get_mailbox() == issuer_mailbox;
+        }
+        return false;
+      });
+
+      // What receive # is the event `e`?
+      const unsigned receive_position = std::count_if(config_K.begin(), config_K.end(), [=](const auto e) {
+        const CommRecvTransition* e_receive = dynamic_cast<const CommRecvTransition*>(e->get_transition());
+        if (e_receive != nullptr) {
+          return e_receive->get_mailbox() == issuer_mailbox;
+        }
+        return false;
+      });
+
+      if (send_position == receive_position) {
+        exC.insert(U->discover_event(std::move(K), test_action));
+      }
+    }
+  } else if (const CommRecvTransition* e_issuer_recv =
+                 dynamic_cast<const CommRecvTransition*>(e_issuer->get_transition());
+             e_issuer_recv != nullptr) {
+
+    for (const auto e : C) {
+      // If the provider of the communication for `CommTest` is a
+      // `CommRecv(m)`, then we only care about `e` if `λ(e) == `CommSend(m)`.
+      // All other actions would be independent with the wait action (including
+      // another `CommRecv` to the same mailbox: `CommWait` is "waiting" for its
+      // corresponding send action)
+      if (e->get_transition()->type_ != Transition::Type::COMM_ASYNC_SEND) {
+        continue;
+      }
+
+      const auto issuer_mailbox        = e_issuer_recv->get_mailbox();
+      const CommSendTransition* e_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
+      if (e_send->get_mailbox() != issuer_mailbox) {
+        continue;
+      }
+
+      // If the `issuer` is not in `config(K)`, this implies that
+      // `WaitAny()` is always disabled in `config(K)`; hence, it
+      // is independent of any transition in `config(K)` (according
+      // to formal definition of independence)
+      const EventSet K    = EventSet({e, pre_event_a_C.value_or(e)});
+      const auto config_K = History(K);
+      if (not config_K.contains(e_issuer)) {
+        continue;
+      }
+
+      // What receive # is the event `e`?
+      const unsigned send_position = std::count_if(config_K.begin(), config_K.end(), [=](const auto e) {
+        const CommSendTransition* e_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
+        if (e_send != nullptr) {
+          return e_send->get_mailbox() == issuer_mailbox;
+        }
+        return false;
+      });
+
+      // What send # is the issuer
+      const unsigned receive_position =
+          std::count_if(e_issuer_history.begin(), e_issuer_history.end(), [=](const auto e) {
+            const CommRecvTransition* e_receive = dynamic_cast<const CommRecvTransition*>(e->get_transition());
+            if (e_receive != nullptr) {
+              return e_receive->get_mailbox() == issuer_mailbox;
+            }
+            return false;
+          });
+
+      if (send_position == receive_position) {
+        exC.insert(U->discover_event(std::move(K), test_action));
+      }
+    }
+  } else {
+    xbt_die("The transition which created the communication on which `%s` waits "
+            "is neither an async send nor an async receive. The current UDPOR "
+            "implementation does not know how to check if `CommWait` is enabled in "
+            "this case. Was a new transition added?",
+            e_issuer->get_transition()->to_string().c_str());
+  }
+  return exC;
+}
+
+EventSet ExtensionSetCalculator::partially_extend_MutexAsyncLock(const Configuration& C, Unfolding* U,
+                                                                 std::shared_ptr<Transition> action)
+{
+  EventSet exC;
+  const auto mutex_lock    = std::static_pointer_cast<MutexTransition>(std::move(action));
+  const auto pre_event_a_C = C.pre_event(mutex_lock->aid_);
+
+  // for each event e in C
+  // 1. If lambda(e) := pre(a) -> add it. Note that if
+  // pre_event_a_C.has_value() == false, this implies `C` is
+  // empty or which we treat as implicitly containing the bottom event
+  if (pre_event_a_C.has_value()) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), mutex_lock);
+    exC.insert(e_prime);
+  } else {
+    const auto e_prime = U->discover_event(EventSet(), mutex_lock);
+    exC.insert(e_prime);
+  }
+
+  // for each event e in C
+  for (const auto e : C) {
+    // Check for other locks on the same mutex
+    if (const MutexTransition* e_mutex = dynamic_cast<const MutexTransition*>(e->get_transition());
+        e_mutex != nullptr) {
+
+      if (e_mutex->type_ == Transition::Type::MUTEX_ASYNC_LOCK && mutex_lock->get_mutex() == e_mutex->get_mutex()) {
+        const EventSet K = EventSet({e, pre_event_a_C.value_or(e)});
+        exC.insert(U->discover_event(std::move(K), mutex_lock));
+      }
+    }
+  }
+  return exC;
+}
+
+EventSet ExtensionSetCalculator::partially_extend_MutexUnlock(const Configuration& C, Unfolding* U,
+                                                              std::shared_ptr<Transition> action)
+{
+  EventSet exC;
+  const auto mutex_unlock  = std::static_pointer_cast<MutexTransition>(std::move(action));
+  const auto pre_event_a_C = C.pre_event(mutex_unlock->aid_);
+
+  // for each event e in C
+  // 1. If lambda(e) := pre(a) -> add it. Note that if
+  // pre_event_a_C.has_value() == false, this implies `C` is
+  // empty or which we treat as implicitly containing the bottom event
+  if (pre_event_a_C.has_value()) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), mutex_unlock);
+    exC.insert(e_prime);
+  } else {
+    const auto e_prime = U->discover_event(EventSet(), mutex_unlock);
+    exC.insert(e_prime);
+  }
+
+  // for each event e in C
+  for (const auto e : C) {
+    // Check for MutexTest
+    if (const MutexTransition* e_mutex = dynamic_cast<const MutexTransition*>(e->get_transition());
+        e_mutex != nullptr) {
+
+      if (e_mutex->type_ == Transition::Type::MUTEX_TEST || e_mutex->type_ == Transition::Type::MUTEX_WAIT) {
+        // TODO: Check if dependent or not
+        // This entails getting information about
+        // the relative position of the mutex in the queue, which
+        // again means we need more context...
+        const EventSet K = EventSet({e, pre_event_a_C.value_or(e)});
+        exC.insert(U->discover_event(std::move(K), mutex_unlock));
+      }
+    }
+  }
+  return exC;
+}
+
+EventSet ExtensionSetCalculator::partially_extend_MutexWait(const Configuration& C, Unfolding* U,
+                                                            std::shared_ptr<Transition> action)
+{
+  EventSet exC;
+  const auto mutex_wait    = std::static_pointer_cast<MutexTransition>(std::move(action));
+  const auto pre_event_a_C = C.pre_event(mutex_wait->aid_);
+
+  // for each event e in C
+  // 1. If lambda(e) := pre(a) -> add it. In the case of MutexWait, we also check that the
+  // actor which is executing the MutexWait is the owner of the mutex
+  if (pre_event_a_C.has_value() && mutex_wait->get_owner() == mutex_wait->aid_) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), mutex_wait);
+    exC.insert(e_prime);
+  } else {
+    const auto e_prime = U->discover_event(EventSet(), mutex_wait);
+    exC.insert(e_prime);
+  }
+
+  // for each event e in C
+  for (const auto e : C) {
+    // Check for any unlocks
+    if (const MutexTransition* e_mutex = dynamic_cast<const MutexTransition*>(e->get_transition());
+        e_mutex != nullptr && e_mutex->type_ == Transition::Type::MUTEX_UNLOCK) {
+      // TODO: Check if dependent or not
+      // This entails getting information about
+      // the relative position of the mutex in the queue, which
+      // again means we need more context...
+      const EventSet K = EventSet({e, pre_event_a_C.value_or(e)});
+      exC.insert(U->discover_event(std::move(K), mutex_wait));
+    }
+  }
+  return exC;
+}
+
+EventSet ExtensionSetCalculator::partially_extend_MutexTest(const Configuration& C, Unfolding* U,
+                                                            std::shared_ptr<Transition> action)
+{
+  EventSet exC;
+  const auto mutex_test    = std::static_pointer_cast<MutexTransition>(std::move(action));
+  const auto pre_event_a_C = C.pre_event(mutex_test->aid_);
+
+  // for each event e in C
+  // 1. If lambda(e) := pre(a) -> add it. Note that if
+  // pre_evevnt_a_C.has_value() == false, this implies `C` is
+  // empty or which we treat as implicitly containing the bottom event
+  if (pre_event_a_C.has_value()) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), mutex_test);
+    exC.insert(e_prime);
+  } else {
+    const auto e_prime = U->discover_event(EventSet(), mutex_test);
+    exC.insert(e_prime);
+  }
+
+  // for each event e in C
+  for (const auto e : C) {
+    // Check for any unlocks
+    if (const MutexTransition* e_mutex = dynamic_cast<const MutexTransition*>(e->get_transition());
+        e_mutex != nullptr && e_mutex->type_ == Transition::Type::MUTEX_UNLOCK) {
+      // TODO: Check if dependent or not
+      // This entails getting information about
+      // the relative position of the mutex in the queue, which
+      // again means we need more context...
+      const EventSet K = EventSet({e, pre_event_a_C.value_or(e)});
+      exC.insert(U->discover_event(std::move(K), mutex_test));
+    }
+  }
+  return exC;
+}
+
+EventSet ExtensionSetCalculator::partially_extend_ActorJoin(const Configuration& C, Unfolding* U,
+                                                            std::shared_ptr<Transition> action)
+{
+  EventSet exC;
+
+  const auto join_action   = std::static_pointer_cast<ActorJoinTransition>(std::move(action));
+  const auto pre_event_a_C = C.pre_event(join_action->aid_);
+
+  // Handling ActorJoin is very simple: it is independent with all
+  // other transitions. Thus the only event it could possibly depend
+  // on is pre(a, C) or the root
+  if (pre_event_a_C.has_value()) {
+    const auto e_prime = U->discover_event(EventSet({pre_event_a_C.value()}), join_action);
+    exC.insert(e_prime);
+  } else {
+    const auto e_prime = U->discover_event(EventSet(), join_action);
+    exC.insert(e_prime);
+  }
+
+  return exC;
 }
 
 } // namespace simgrid::mc::udpor
index 02b8669..f257a7f 100644 (file)
@@ -30,6 +30,13 @@ private:
   static EventSet partially_extend_CommWait(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
   static EventSet partially_extend_CommTest(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
 
+  static EventSet partially_extend_MutexAsyncLock(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
+  static EventSet partially_extend_MutexWait(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
+  static EventSet partially_extend_MutexTest(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
+  static EventSet partially_extend_MutexUnlock(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
+
+  static EventSet partially_extend_ActorJoin(const Configuration&, Unfolding*, std::shared_ptr<Transition>);
+
 public:
   static EventSet partially_extend(const Configuration&, Unfolding*, const std::shared_ptr<Transition>);
 };
diff --git a/src/mc/explo/udpor/ExtensionSet_test.cpp b/src/mc/explo/udpor/ExtensionSet_test.cpp
new file mode 100644 (file)
index 0000000..2f778bc
--- /dev/null
@@ -0,0 +1,319 @@
+/* Copyright (c) 2017-2023. The SimGrid Team. All rights reserved.               */
+
+/* This program is free software; you can redistribute it and/or modify it
+ * under the terms of the license (GNU LGPL) which comes with this package. */
+
+#include "src/3rd-party/catch.hpp"
+#include "src/mc/explo/udpor/Configuration.hpp"
+#include "src/mc/explo/udpor/ExtensionSetCalculator.hpp"
+#include "src/mc/explo/udpor/History.hpp"
+#include "src/mc/explo/udpor/Unfolding.hpp"
+
+using namespace simgrid::mc;
+using namespace simgrid::mc::udpor;
+
+TEST_CASE("simgrid::mc::udpor: Testing Computation with AsyncSend/AsyncReceive Only")
+{
+  // This test checks that the unfolding constructed for the very
+  // simple program described below is extended correctly. Each
+  // step of UDPOR is observed to ensure that computations are carried
+  // out correctly. The program described is simply:
+  //
+  //      1                 2
+  // AsyncSend(m)      AsyncRecv(m)
+  //
+  // The unfolding of the simple program is as follows:
+  //
+  //                         ⊥
+  //                  /            /
+  //       (a) 1: AsyncSend(m)  (b) 2: AsyncRecv(m)
+
+  const int times_considered = 0;
+  const int tag              = 0;
+  const unsigned mbox        = 0;
+  const uintptr_t comm       = 0;
+
+  Unfolding U;
+
+  SECTION("Computing ex({⊥}) with 1: AsyncSend")
+  {
+    // Consider the extension with `1: AsyncSend(m)`
+    Configuration C;
+    aid_t issuer          = 1;
+    const uintptr_t sbuff = 0;
+    const size_t size     = 100;
+
+    const auto async_send =
+        std::make_shared<CommSendTransition>(issuer, times_considered, comm, mbox, sbuff, size, tag);
+    const auto incremental_exC = ExtensionSetCalculator::partially_extend(C, &U, async_send);
+
+    // Check that the events have been added to `U`
+    REQUIRE(U.size() == 1);
+
+    // Make assertions about the contents of ex(C)
+    UnfoldingEvent e(EventSet(), async_send);
+    REQUIRE(incremental_exC.contains_equivalent_to(&e));
+  }
+
+  SECTION("Computing ex({⊥}) with 2: AsyncRecv")
+  {
+    // Consider the extension with `2: AsyncRecv(m)`
+    Configuration C;
+    aid_t issuer          = 2;
+    const uintptr_t rbuff = 0;
+
+    const auto async_recv      = std::make_shared<CommRecvTransition>(issuer, times_considered, comm, mbox, rbuff, tag);
+    const auto incremental_exC = ExtensionSetCalculator::partially_extend(C, &U, async_recv);
+
+    // Check that the events have been added to `U`
+    REQUIRE(U.size() == 1);
+
+    // Make assertions about the contents of ex(C)
+    UnfoldingEvent e(EventSet(), async_recv);
+    REQUIRE(incremental_exC.contains_equivalent_to(&e));
+  }
+
+  SECTION("Computing ex({⊥}) fully")
+  {
+    // Consider the extension with `1: AsyncSend(m)`
+    Configuration C;
+    const uintptr_t rbuff = 0;
+    const uintptr_t sbuff = 0;
+    const size_t size     = 100;
+
+    const auto async_send = std::make_shared<CommSendTransition>(1, times_considered, comm, mbox, sbuff, size, tag);
+    const auto incremental_exC_send = ExtensionSetCalculator::partially_extend(C, &U, async_send);
+
+    // Check that the events have been added to `U`
+    REQUIRE(U.size() == 1);
+
+    // Make assertions about the contents of ex(C)
+    UnfoldingEvent e_send(EventSet(), async_send);
+    REQUIRE(incremental_exC_send.contains_equivalent_to(&e_send));
+
+    // Consider the extension with `2: AsyncRecv(m)`
+    const auto async_recv           = std::make_shared<CommRecvTransition>(2, times_considered, comm, mbox, rbuff, tag);
+    const auto incremental_exC_recv = ExtensionSetCalculator::partially_extend(C, &U, async_recv);
+
+    // Check that the events have been added to `U`
+    REQUIRE(U.size() == 2);
+
+    // Make assertions about the contents of ex(C)
+    UnfoldingEvent e_recv(EventSet(), async_recv);
+    REQUIRE(incremental_exC_recv.contains_equivalent_to(&e_recv));
+  }
+
+  SECTION("Computing the full sequence of extensions")
+  {
+    Configuration C;
+    const uintptr_t rbuff = 0;
+    const uintptr_t sbuff = 0;
+    const size_t size     = 100;
+
+    // Consider the extension with `1: AsyncSend(m)`
+    const auto async_send = std::make_shared<CommSendTransition>(1, times_considered, comm, mbox, sbuff, size, tag);
+    const auto incremental_exC_send = ExtensionSetCalculator::partially_extend(C, &U, async_send);
+
+    // Check that event `a` has been added to `U`
+    REQUIRE(U.size() == 1);
+    UnfoldingEvent e_send(EventSet(), async_send);
+    REQUIRE(incremental_exC_send.contains_equivalent_to(&e_send));
+
+    // Consider the extension with `2: AsyncRecv(m)`
+    const auto async_recv           = std::make_shared<CommRecvTransition>(2, times_considered, comm, mbox, rbuff, tag);
+    const auto incremental_exC_recv = ExtensionSetCalculator::partially_extend(C, &U, async_recv);
+
+    // Check that event `b` has been added to `U`
+    REQUIRE(U.size() == 2);
+    UnfoldingEvent e_recv(EventSet(), async_recv);
+    REQUIRE(incremental_exC_recv.contains_equivalent_to(&e_recv));
+
+    // At this point, UDPOR will pick one of the two events equivalent to
+    // `e_recv` and e`_send`
+
+    // Suppose it picks the event labeled `a` in the graph above first
+    // (ultimately, UDPOR will choose to search both directions since
+    // {⊥, b} will be an alternative to {⊥, a})
+
+    const auto* e_a = *incremental_exC_send.begin();
+    const auto* e_b = *incremental_exC_recv.begin();
+
+    SECTION("Pick `a` first (ex({⊥, a}))")
+    {
+      // After picking `a`, we need only consider the extensions
+      // possible with `2: AsyncRecv(m)` since actor 1 no longer
+      // has any actions to run
+      Configuration C_with_send{e_a};
+      const auto incremental_exC_with_send = ExtensionSetCalculator::partially_extend(C_with_send, &U, async_recv);
+
+      // Check that event `b` has not been duplicated
+      REQUIRE(U.size() == 2);
+
+      // Indeed, in this case we assert that the SAME identity has been
+      // supplied by the unfolding (it should note that `ex({⊥, a})`
+      // and `ex({⊥})` have an overlapping event `b`)
+      REQUIRE(incremental_exC_with_send.contains(e_b));
+    }
+
+    SECTION("Pick `b` first (ex({⊥, b}))")
+    {
+      // After picking `b`, we need only consider the extensions
+      // possible with `1: AsyncSend(m)` since actor 2 no longer
+      // has any actions to run
+      Configuration C_with_recv{e_b};
+      const auto incremental_exC_with_recv = ExtensionSetCalculator::partially_extend(C_with_recv, &U, async_send);
+
+      // Check that event `a` has not been duplicated
+      REQUIRE(U.size() == 2);
+
+      // Indeed, in this case we assert that the SAME identity has been
+      // supplied by the unfolding (it should note that `ex({⊥, b})`
+      // and `ex({⊥})` have an overlapping event `a`)
+      REQUIRE(incremental_exC_with_recv.contains(e_a));
+    }
+  }
+}
+
+TEST_CASE("simgrid::mc::udpor: Testing Waits, Receives, and Sends")
+{
+  // We're going to follow UDPOR down one path of computation
+  // in a relatively simple program (although the unfolding quickly
+  // becomes quite complex)
+  //
+  //      1                 2
+  // AsyncSend(m)      AsyncRecv(m)
+  // Wait(m)           Wait(m)
+  //
+  // The unfolding of the simple program is as follows:
+  //                         ⊥
+  //                  /            /
+  //       (a) 1: AsyncSend(m)  (b) 2: AsyncRecv(m)
+  //                |   \        /        |
+  //                |      \  /          |
+  //               |      /   \          |
+  //               |    /      \         |
+  //       (c) 1: Wait(m)       (d) 2: Wait(m)
+  const int times_considered = 0;
+  const int tag              = 0;
+  const unsigned mbox        = 0;
+  const uintptr_t comm       = 0x800;
+  const uintptr_t rbuff      = 0x200;
+  const uintptr_t sbuff      = 0x400;
+  const size_t size          = 100;
+  const bool timeout         = false;
+
+  Unfolding U;
+  const auto comm_send = std::make_shared<CommSendTransition>(1, times_considered, comm, mbox, sbuff, size, tag);
+  const auto comm_recv = std::make_shared<CommRecvTransition>(2, times_considered, comm, mbox, rbuff, tag);
+  const auto comm_wait_1 =
+      std::make_shared<CommWaitTransition>(1, times_considered, timeout, comm, 1, 2, mbox, sbuff, rbuff, size);
+  const auto comm_wait_2 =
+      std::make_shared<CommWaitTransition>(2, times_considered, timeout, comm, 1, 2, mbox, sbuff, rbuff, size);
+
+  // 1. UDPOR will attempt to expand first ex({⊥})
+
+  // --- ex({⊥}) ---
+  const auto incremental_exC_send = ExtensionSetCalculator::partially_extend(Configuration(), &U, comm_send);
+  { // Assert that event `a` has been added
+    UnfoldingEvent e_send(EventSet(), comm_send);
+    REQUIRE(incremental_exC_send.size() == 1);
+    REQUIRE(incremental_exC_send.contains_equivalent_to(&e_send));
+    REQUIRE(U.size() == 1);
+  }
+
+  const auto incremental_exC_recv = ExtensionSetCalculator::partially_extend(Configuration(), &U, comm_recv);
+  { // Assert that event `b` has been added
+    UnfoldingEvent e_recv(EventSet(), comm_recv);
+    REQUIRE(incremental_exC_recv.size() == 1);
+    REQUIRE(incremental_exC_recv.contains_equivalent_to(&e_recv));
+    REQUIRE(U.size() == 2);
+  }
+  // --- ex({⊥}) ---
+
+  // 2. UDPOR will then attempt to expand ex({⊥, a}) or ex({⊥, b}). Both have
+  // parallel effects and should simply return events already added to ex(C)
+  //
+  // NOTE: Note that only once actor is enabled in both cases, meaning that
+  // we need only consider one incremental expansion for each
+
+  const auto* e_a = *incremental_exC_send.begin();
+  const auto* e_b = *incremental_exC_recv.begin();
+
+  // --- ex({⊥, a}) ---
+  const auto incremental_exC_recv2 = ExtensionSetCalculator::partially_extend(Configuration({e_a}), &U, comm_recv);
+  { // Assert that no event has been added and that
+    // e_b is contained in the extension set
+    UnfoldingEvent e_send(EventSet(), comm_send);
+    REQUIRE(incremental_exC_recv2.size() == 1);
+    REQUIRE(incremental_exC_recv2.contains(e_b));
+
+    // Here, `e_a` shouldn't be added again
+    REQUIRE(U.size() == 2);
+  }
+  // --- ex({⊥, a}) ---
+
+  // --- ex({⊥, b}) ---
+  const auto incremental_exC_send2 = ExtensionSetCalculator::partially_extend(Configuration({e_b}), &U, comm_send);
+  { // Assert that no event has been added and that
+    // e_a is contained in the extension set
+    REQUIRE(incremental_exC_send2.size() == 1);
+    REQUIRE(incremental_exC_send2.contains(e_a));
+
+    // Here, `e_b` shouldn't be added again
+    REQUIRE(U.size() == 2);
+  }
+  // --- ex({⊥, b}) ---
+
+  // 3. Expanding from ex({⊥, a, b}) brings in both `CommWait` events since they
+  // become enabled as soon as the communication has been paired
+
+  // --- ex({⊥, a, b}) ---
+  const auto incremental_exC_wait_actor_1 =
+      ExtensionSetCalculator::partially_extend(Configuration({e_a, e_b}), &U, comm_wait_1);
+  { // Assert that events `c` has been added
+    UnfoldingEvent e_wait_1(EventSet({e_a, e_b}), comm_wait_1);
+    REQUIRE(incremental_exC_wait_actor_1.size() == 1);
+    REQUIRE(incremental_exC_wait_actor_1.contains_equivalent_to(&e_wait_1));
+    REQUIRE(U.size() == 3);
+  }
+
+  const auto incremental_exC_wait_actor_2 =
+      ExtensionSetCalculator::partially_extend(Configuration({e_a, e_b}), &U, comm_wait_2);
+  { // Assert that events `d` has been added
+    UnfoldingEvent e_wait_2(EventSet({e_a, e_b}), comm_wait_2);
+    REQUIRE(incremental_exC_wait_actor_2.size() == 1);
+    REQUIRE(incremental_exC_wait_actor_2.contains_equivalent_to(&e_wait_2));
+    REQUIRE(U.size() == 4);
+  }
+  // --- ex({⊥, a, b}) ---
+
+  // 4. Expanding from either wait action should simply yield the other event
+  // with a wait action associated with it.
+  // This is analogous to the scenario before with send and receive
+  // ex({⊥, a, b, c}) or ex({⊥, a, b, d})
+
+  const auto* e_c = *incremental_exC_wait_actor_1.begin();
+  const auto* e_d = *incremental_exC_wait_actor_2.begin();
+
+  // --- ex({⊥, a, b, d}) ---
+  const auto incremental_exC_wait_actor_1_2 =
+      ExtensionSetCalculator::partially_extend(Configuration({e_a, e_b, e_d}), &U, comm_wait_1);
+  { // Assert that no event has been added and that
+    // `e_c` is contained in the extension set
+    REQUIRE(incremental_exC_wait_actor_1_2.size() == 1);
+    REQUIRE(incremental_exC_wait_actor_1_2.contains(e_c));
+    REQUIRE(U.size() == 4);
+  }
+  // --- ex({⊥, a, b, d}) ---
+
+  // --- ex({⊥, a, b, c}) ---
+  const auto incremental_exC_wait_actor_2_2 =
+      ExtensionSetCalculator::partially_extend(Configuration({e_a, e_b, e_c}), &U, comm_wait_2);
+  { // Assert that no event has been added and that
+    // `e_d` is contained in the extension set
+    REQUIRE(incremental_exC_wait_actor_2_2.size() == 1);
+    REQUIRE(incremental_exC_wait_actor_2_2.contains(e_d));
+    REQUIRE(U.size() == 4);
+  }
+  // --- ex({⊥, a, b, c}) ---
+}
\ No newline at end of file
index b1d7382..fd4dc2e 100644 (file)
@@ -20,6 +20,8 @@ UnfoldingEvent::UnfoldingEvent(std::initializer_list<const UnfoldingEvent*> init
 UnfoldingEvent::UnfoldingEvent(EventSet immediate_causes, std::shared_ptr<Transition> transition)
     : associated_transition(std::move(transition)), immediate_causes(std::move(immediate_causes))
 {
+  static uint64_t event_id = 0;
+  this->id                 = ++event_id;
 }
 
 bool UnfoldingEvent::operator==(const UnfoldingEvent& other) const
@@ -50,11 +52,13 @@ std::string UnfoldingEvent::to_string() const
 
   dependencies_string += "[";
   for (const auto* e : immediate_causes) {
+    dependencies_string += " ";
     dependencies_string += e->to_string();
+    dependencies_string += " and ";
   }
   dependencies_string += "]";
 
-  return xbt::string_printf("(%p) Actor %ld: %s (%zu dependencies: %s)", this, associated_transition->aid_,
+  return xbt::string_printf("Event %lu, Actor %ld: %s (%zu dependencies: %s)", this->id, associated_transition->aid_,
                             associated_transition->to_string().c_str(), immediate_causes.size(),
                             dependencies_string.c_str());
 }
index bad411c..5486709 100644 (file)
@@ -49,9 +49,10 @@ public:
   bool is_dependent_with(const Transition*) const;
   bool is_dependent_with(const UnfoldingEvent* other) const;
 
+  unsigned get_id() const { return this->id; }
+  aid_t get_actor() const { return get_transition()->aid_; }
   const EventSet& get_immediate_causes() const { return this->immediate_causes; }
   Transition* get_transition() const { return this->associated_transition.get(); }
-  aid_t get_actor() const { return get_transition()->aid_; }
 
   void set_transition(std::shared_ptr<Transition> t) { this->associated_transition = std::move(t); }
 
@@ -91,6 +92,12 @@ private:
    * so on.
    */
   EventSet immediate_causes;
+
+  /**
+   * @brief An identifier which is used to sort events
+   * deterministically
+   */
+  uint64_t id = 0;
 };
 
 } // namespace simgrid::mc::udpor
index 83061b0..e2c46f4 100644 (file)
@@ -71,7 +71,9 @@ void PageStore::resize(std::size_t size)
                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
     xbt_assert(new_memory != MAP_FAILED, "Could not mremap snapshot pages.");
     // Check if expanding worked
-    if (new_memory != (char*)this->memory_ + old_bytesize) {
+    if (new_memory == (char*)this->memory_ + old_bytesize) {
+      new_memory = this->memory_;
+    } else {
       // New memory segment could not be put at the end of this->memory_,
       // so cancel this one and try to relocate everything and copy data
       munmap(new_memory, new_bytesize - old_bytesize);
index d8c481e..f6886c3 100644 (file)
@@ -18,6 +18,20 @@ XBT_LOG_NEW_DEFAULT_SUBCATEGORY(mc_trans_comm, mc_transition,
 
 namespace simgrid::mc {
 
+CommWaitTransition::CommWaitTransition(aid_t issuer, int times_considered, bool timeout_, uintptr_t comm_,
+                                       aid_t sender_, aid_t receiver_, unsigned mbox_, uintptr_t sbuff_,
+                                       uintptr_t rbuff_, size_t size_)
+    : Transition(Type::COMM_WAIT, issuer, times_considered)
+    , timeout_(timeout_)
+    , comm_(comm_)
+    , sender_(sender_)
+    , receiver_(receiver_)
+    , mbox_(mbox_)
+    , sbuff_(sbuff_)
+    , rbuff_(rbuff_)
+    , size_(size_)
+{
+}
 CommWaitTransition::CommWaitTransition(aid_t issuer, int times_considered, std::stringstream& stream)
     : Transition(Type::COMM_WAIT, issuer, times_considered)
 {
@@ -54,6 +68,19 @@ bool CommWaitTransition::depends(const Transition* other) const
 
   return false; // Comm transitions are INDEP with non-comm transitions
 }
+CommTestTransition::CommTestTransition(aid_t issuer, int times_considered, uintptr_t comm_, aid_t sender_,
+                                       aid_t receiver_, unsigned mbox_, uintptr_t sbuff_, uintptr_t rbuff_,
+                                       size_t size_)
+    : Transition(Type::COMM_TEST, issuer, times_considered)
+    , comm_(comm_)
+    , sender_(sender_)
+    , receiver_(receiver_)
+    , mbox_(mbox_)
+    , sbuff_(sbuff_)
+    , rbuff_(rbuff_)
+    , size_(size_)
+{
+}
 CommTestTransition::CommTestTransition(aid_t issuer, int times_considered, std::stringstream& stream)
     : Transition(Type::COMM_TEST, issuer, times_considered)
 {
@@ -95,6 +122,15 @@ bool CommTestTransition::depends(const Transition* other) const
   return false; // Comm transitions are INDEP with non-comm transitions
 }
 
+CommRecvTransition::CommRecvTransition(aid_t issuer, int times_considered, uintptr_t comm_, unsigned mbox_,
+                                       uintptr_t rbuff_, int tag_)
+    : Transition(Type::COMM_ASYNC_RECV, issuer, times_considered)
+    , comm_(comm_)
+    , mbox_(mbox_)
+    , rbuff_(rbuff_)
+    , tag_(tag_)
+{
+}
 CommRecvTransition::CommRecvTransition(aid_t issuer, int times_considered, std::stringstream& stream)
     : Transition(Type::COMM_ASYNC_RECV, issuer, times_considered)
 {
@@ -130,6 +166,11 @@ bool CommRecvTransition::depends(const Transition* other) const
     if ((aid_ != test->sender_) && (aid_ != test->receiver_) && (test->rbuff_ != rbuff_))
       return false;
 
+    // If the test is checking a paired comm already, we're independent!
+    // If we happen to make up that pair, then we're dependent...
+    if (test->comm_ != comm_)
+      return false;
+
     return true; // DEP with other send transitions
   }
 
@@ -143,12 +184,27 @@ bool CommRecvTransition::depends(const Transition* other) const
     if ((aid_ != wait->sender_) && (aid_ != wait->receiver_) && (wait->rbuff_ != rbuff_))
       return false;
 
+    // If the wait is waiting on a paired comm already, we're independent!
+    // If we happen to make up that pair, then we're dependent...
+    if ((aid_ != wait->aid_) && wait->comm_ != comm_)
+      return false;
+
     return true; // DEP with other wait transitions
   }
 
   return false; // Comm transitions are INDEP with non-comm transitions
 }
 
+CommSendTransition::CommSendTransition(aid_t issuer, int times_considered, uintptr_t comm_, unsigned mbox_,
+                                       uintptr_t sbuff_, size_t size_, int tag_)
+    : Transition(Type::COMM_ASYNC_SEND, issuer, times_considered)
+    , comm_(comm_)
+    , mbox_(mbox_)
+    , sbuff_(sbuff_)
+    , size_(size_)
+    , tag_(tag_)
+{
+}
 CommSendTransition::CommSendTransition(aid_t issuer, int times_considered, std::stringstream& stream)
     : Transition(Type::COMM_ASYNC_SEND, issuer, times_considered)
 {
@@ -186,6 +242,11 @@ bool CommSendTransition::depends(const Transition* other) const
     if ((aid_ != test->sender_) && (aid_ != test->receiver_) && (test->sbuff_ != sbuff_))
       return false;
 
+    // If the test is checking a paired comm already, we're independent!
+    // If we happen to make up that pair, then we're dependent...
+    if (test->comm_ != comm_)
+      return false;
+
     return true; // DEP with other test transitions
   }
 
@@ -199,6 +260,11 @@ bool CommSendTransition::depends(const Transition* other) const
     if ((aid_ != wait->sender_) && (aid_ != wait->receiver_) && (wait->sbuff_ != sbuff_))
       return false;
 
+    // If the wait is waiting on a paired comm already, we're independent!
+    // If we happen to make up that pair, then we're dependent...
+    if ((aid_ != wait->aid_) && wait->comm_ != comm_)
+      return false;
+
     return true; // DEP with other wait transitions
   }
 
index f652d03..597a530 100644 (file)
@@ -33,6 +33,8 @@ class CommWaitTransition : public Transition {
   friend CommTestTransition;
 
 public:
+  CommWaitTransition(aid_t issuer, int times_considered, bool timeout_, uintptr_t comm_, aid_t sender_, aid_t receiver_,
+                     unsigned mbox_, uintptr_t sbuff_, uintptr_t rbuff_, size_t size_);
   CommWaitTransition(aid_t issuer, int times_considered, std::stringstream& stream);
   std::string to_string(bool verbose) const override;
   bool depends(const Transition* other) const override;
@@ -65,6 +67,8 @@ class CommTestTransition : public Transition {
   friend CommRecvTransition;
 
 public:
+  CommTestTransition(aid_t issuer, int times_considered, uintptr_t comm_, aid_t sender_, aid_t receiver_,
+                     unsigned mbox_, uintptr_t sbuff_, uintptr_t rbuff_, size_t size_);
   CommTestTransition(aid_t issuer, int times_considered, std::stringstream& stream);
   std::string to_string(bool verbose) const override;
   bool depends(const Transition* other) const override;
@@ -92,6 +96,7 @@ class CommRecvTransition : public Transition {
   int tag_;
 
 public:
+  CommRecvTransition(aid_t issuer, int times_considered, uintptr_t comm_, unsigned mbox_, uintptr_t rbuff_, int tag_);
   CommRecvTransition(aid_t issuer, int times_considered, std::stringstream& stream);
   std::string to_string(bool verbose) const override;
   bool depends(const Transition* other) const override;
@@ -114,6 +119,8 @@ class CommSendTransition : public Transition {
   int tag_;
 
 public:
+  CommSendTransition(aid_t issuer, int times_considered, uintptr_t comm_, unsigned mbox_, uintptr_t sbuff_,
+                     size_t size_, int tag_);
   CommSendTransition(aid_t issuer, int times_considered, std::stringstream& stream);
   std::string to_string(bool verbose) const override;
   bool depends(const Transition* other) const override;
index 87e4865..8742e65 100644 (file)
@@ -29,6 +29,9 @@ public:
   std::string to_string(bool verbose) const override;
   MutexTransition(aid_t issuer, int times_considered, Type type, std::stringstream& stream);
   bool depends(const Transition* other) const override;
+
+  uintptr_t get_mutex() const { return this->mutex_; }
+  aid_t get_owner() const { return this->owner_; }
 };
 
 class SemaphoreTransition : public Transition {
index 6c71e1d..6023103 100644 (file)
@@ -138,8 +138,8 @@ set(STATEFUL_MC_UNIT_TESTS src/mc/sosp/Snapshot_test.cpp
                            src/mc/sosp/PageStore_test.cpp
                            src/mc/explo/udpor/Unfolding_test.cpp
                            src/mc/explo/udpor/UnfoldingEvent_test.cpp
-
                            src/mc/explo/udpor/EventSet_test.cpp
+                           src/mc/explo/udpor/ExtensionSet_test.cpp
                            src/mc/explo/udpor/History_test.cpp
                            src/mc/explo/udpor/Configuration_test.cpp)