model: bugfix - release sequences - handle Thread completion
[c11tester.git] / model.cc
index 64c1daa5d8f2b52aa561b50cbd2a07a65853ec84..a9d94776cc3ec090d821caad3133441f2d2e2bcc 100644 (file)
--- a/model.cc
+++ b/model.cc
@@ -20,10 +20,10 @@ ModelChecker *model;
 /** @brief Constructor */
 ModelChecker::ModelChecker(struct model_params params) :
        /* Initialize default scheduler */
+       params(params),
        scheduler(new Scheduler()),
        num_executions(0),
        num_feasible_executions(0),
-       params(params),
        diverge(NULL),
        action_trace(new action_list_t()),
        thread_map(new HashTable<int, Thread *, int>()),
@@ -32,7 +32,7 @@ ModelChecker::ModelChecker(struct model_params params) :
        obj_thrd_map(new HashTable<void *, std::vector<action_list_t>, uintptr_t, 4 >()),
        promises(new std::vector<Promise *>()),
        futurevalues(new std::vector<struct PendingFutureValue>()),
-       lazy_sync_with_release(new HashTable<void *, action_list_t, uintptr_t, 4>()),
+       pending_acq_rel_seq(new std::vector<ModelAction *>()),
        thrd_last_action(new std::vector<ModelAction *>(1)),
        node_stack(new NodeStack()),
        mo_graph(new CycleGraph()),
@@ -44,8 +44,6 @@ ModelChecker::ModelChecker(struct model_params params) :
        priv = (struct model_snapshot_members *)calloc(1, sizeof(*priv));
        /* First thread created will have id INITIAL_THREAD_ID */
        priv->next_thread_id = INITIAL_THREAD_ID;
-
-       lazy_sync_size = &priv->lazy_sync_size;
 }
 
 /** @brief Destructor */
@@ -64,7 +62,7 @@ ModelChecker::~ModelChecker()
                delete (*promises)[i];
        delete promises;
 
-       delete lazy_sync_with_release;
+       delete pending_acq_rel_seq;
 
        delete thrd_last_action;
        delete node_stack;
@@ -248,8 +246,6 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
  *
  * @param the ModelAction to find backtracking points for.
  */
-
-
 void ModelChecker::set_backtracking(ModelAction *act)
 {
        Thread *t = get_thread(act);
@@ -277,6 +273,20 @@ void ModelChecker::set_backtracking(ModelAction *act)
                if (node->has_been_explored(tid))
                        continue;
 
+               /* See if fairness allows */
+               if (model->params.fairwindow != 0 && !node->has_priority(tid)) {
+                       bool unfair=false;
+                       for(int t=0;t<node->get_num_threads();t++) {
+                               thread_id_t tother=int_to_id(t);
+                               if (node->is_enabled(tother) && node->has_priority(tother)) {
+                                       unfair=true;
+                                       break;
+                               }
+                       }
+                       if (unfair)
+                               continue;
+               }
+
                /* Cache the latest backtracking point */
                if (!priv->next_backtrack || *prev > *priv->next_backtrack)
                        priv->next_backtrack = prev;
@@ -433,6 +443,63 @@ bool ModelChecker::process_write(ModelAction *curr)
        return updated_mod_order || updated_promises;
 }
 
+/**
+ * @brief Process the current action for thread-related activity
+ *
+ * Performs current-action processing for a THREAD_* ModelAction. Proccesses
+ * may include setting Thread status, completing THREAD_FINISH/THREAD_JOIN
+ * synchronization, etc.  This function is a no-op for non-THREAD actions
+ * (e.g., ATOMIC_{READ,WRITE,RMW,LOCK}, etc.)
+ *
+ * @param curr The current action
+ * @return True if synchronization was updated
+ */
+bool ModelChecker::process_thread_action(ModelAction *curr)
+{
+       bool synchronized = false;
+
+       switch (curr->get_type()) {
+       case THREAD_CREATE: {
+               Thread *th = (Thread *)curr->get_location();
+               th->set_creation(curr);
+               break;
+       }
+       case THREAD_JOIN: {
+               Thread *waiting, *blocking;
+               waiting = get_thread(curr);
+               blocking = (Thread *)curr->get_location();
+               if (!blocking->is_complete()) {
+                       blocking->push_wait_list(curr);
+                       scheduler->sleep(waiting);
+               } else {
+                       do_complete_join(curr);
+                       synchronized = true;
+               }
+               break;
+       }
+       case THREAD_FINISH: {
+               Thread *th = get_thread(curr);
+               while (!th->wait_list_empty()) {
+                       ModelAction *act = th->pop_wait_list();
+                       Thread *wake = get_thread(act);
+                       scheduler->wake(wake);
+                       do_complete_join(act);
+                       synchronized = true;
+               }
+               th->complete();
+               break;
+       }
+       case THREAD_START: {
+               check_promises(NULL, curr->get_cv());
+               break;
+       }
+       default:
+               break;
+       }
+
+       return synchronized;
+}
+
 /**
  * Initialize the current action by performing one or more of the following
  * actions, as appropriate: merging RMWR and RMWC/RMW actions, stepping forward
@@ -490,7 +557,6 @@ ModelAction * ModelChecker::initialize_curr_action(ModelAction *curr)
  * @param curr is the ModelAction to check whether it is enabled.
  * @return a bool that indicates whether the action is enabled.
  */
-
 bool ModelChecker::check_action_enabled(ModelAction *curr) {
        if (curr->is_lock()) {
                std::mutex * lock = (std::mutex *)curr->get_location();
@@ -524,8 +590,8 @@ Thread * ModelChecker::check_current_action(ModelAction *curr)
        bool second_part_of_rmw = curr->is_rmwc() || curr->is_rmw();
 
        if (!check_action_enabled(curr)) {
-               //we'll make the execution look like we chose to run this action
-               //much later...when a lock is actually available to relese
+               /* Make the execution look like we chose to run this action
+                * much later, when a lock is actually available to release */
                get_current_thread()->set_pending(curr);
                remove_thread(get_current_thread());
                return get_next_thread(NULL);
@@ -542,44 +608,6 @@ Thread * ModelChecker::check_current_action(ModelAction *curr)
                build_reads_from_past(curr);
        curr = newcurr;
 
-       /* Thread specific actions */
-       switch (curr->get_type()) {
-       case THREAD_CREATE: {
-               Thread *th = (Thread *)curr->get_location();
-               th->set_creation(curr);
-               break;
-       }
-       case THREAD_JOIN: {
-               Thread *waiting, *blocking;
-               waiting = get_thread(curr);
-               blocking = (Thread *)curr->get_location();
-               if (!blocking->is_complete()) {
-                       blocking->push_wait_list(curr);
-                       scheduler->sleep(waiting);
-               } else {
-                       do_complete_join(curr);
-               }
-               break;
-       }
-       case THREAD_FINISH: {
-               Thread *th = get_thread(curr);
-               while (!th->wait_list_empty()) {
-                       ModelAction *act = th->pop_wait_list();
-                       Thread *wake = get_thread(act);
-                       scheduler->wake(wake);
-                       do_complete_join(act);
-               }
-               th->complete();
-               break;
-       }
-       case THREAD_START: {
-               check_promises(NULL, curr->get_cv());
-               break;
-       }
-       default:
-               break;
-       }
-
        work_queue_t work_queue(1, CheckCurrWorkEntry(curr));
 
        while (!work_queue.empty()) {
@@ -590,6 +618,9 @@ Thread * ModelChecker::check_current_action(ModelAction *curr)
                case WORK_CHECK_CURR_ACTION: {
                        ModelAction *act = work.action;
                        bool updated = false;
+
+                       process_thread_action(curr);
+
                        if (act->is_read() && process_read(act, second_part_of_rmw))
                                updated = true;
 
@@ -677,7 +708,7 @@ bool ModelChecker::promises_expired() {
 /** @return whether the current partial trace must be a prefix of a
  * feasible trace. */
 bool ModelChecker::isfeasibleprefix() {
-       return promises->size() == 0 && *lazy_sync_size == 0;
+       return promises->size() == 0 && pending_acq_rel_seq->size() == 0;
 }
 
 /** @return whether the current partial trace is feasible. */
@@ -887,7 +918,6 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const ModelAction *rf
  * @param rf is the write ModelAction that curr reads from.
  *
  */
-
 void ModelChecker::post_r_modification_order(ModelAction *curr, const ModelAction *rf)
 {
        std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
@@ -1027,7 +1057,6 @@ bool ModelChecker::w_modification_order(ModelAction *curr)
 /** Arbitrary reads from the future are not allowed.  Section 29.3
  * part 9 places some constraints.  This method checks one result of constraint
  * constraint.  Others require compiler support. */
-
 bool ModelChecker::thin_air_constraint_may_allow(const ModelAction * writer, const ModelAction *reader) {
        if (!writer->is_rmw())
                return true;
@@ -1069,24 +1098,32 @@ bool ModelChecker::thin_air_constraint_may_allow(const ModelAction * writer, con
  */
 bool ModelChecker::release_seq_head(const ModelAction *rf, rel_heads_list_t *release_heads) const
 {
-       if (!rf) {
-               /* read from future: need to settle this later */
-               return false; /* incomplete */
-       }
+       while (rf) {
+               ASSERT(rf->is_write());
 
-       ASSERT(rf->is_write());
+               if (rf->is_release())
+                       release_heads->push_back(rf);
+               if (!rf->is_rmw())
+                       break; /* End of RMW chain */
 
-       if (rf->is_release())
-               release_heads->push_back(rf);
-       if (rf->is_rmw()) {
-               /* We need a RMW action that is both an acquire and release to stop */
                /** @todo Need to be smarter here...  In the linux lock
                 * example, this will run to the beginning of the program for
                 * every acquire. */
+               /** @todo The way to be smarter here is to keep going until 1
+                * thread has a release preceded by an acquire and you've seen
+                *       both. */
+
+               /* acq_rel RMW is a sufficient stopping condition */
                if (rf->is_acquire() && rf->is_release())
                        return true; /* complete */
-               return release_seq_head(rf->get_reads_from(), release_heads);
+
+               rf = rf->get_reads_from();
+       };
+       if (!rf) {
+               /* read from future: need to settle this later */
+               return false; /* incomplete */
        }
+
        if (rf->is_release())
                return true; /* complete */
 
@@ -1124,7 +1161,8 @@ bool ModelChecker::release_seq_head(const ModelAction *rf, rel_heads_list_t *rel
                bool future_ordered = false;
 
                ModelAction *last = get_last_action(int_to_id(i));
-               if (last && rf->happens_before(last))
+               if (last && (rf->happens_before(last) ||
+                               last->get_type() == THREAD_FINISH))
                        future_ordered = true;
 
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
@@ -1185,10 +1223,7 @@ void ModelChecker::get_release_seq_heads(ModelAction *act, rel_heads_list_t *rel
        complete = release_seq_head(rf, release_heads);
        if (!complete) {
                /* add act to 'lazy checking' list */
-               action_list_t *list;
-               list = lazy_sync_with_release->get_safe_ptr(act->get_location());
-               list->push_back(act);
-               (*lazy_sync_size)++;
+               pending_acq_rel_seq->push_back(act);
        }
 }
 
@@ -1199,7 +1234,7 @@ void ModelChecker::get_release_seq_heads(ModelAction *act, rel_heads_list_t *rel
  * modification order information is present at the time an action occurs.
  *
  * @param location The location/object that should be checked for release
- * sequence resolutions
+ * sequence resolutions. A NULL value means to check all locations.
  * @param work_queue The work queue to which to add work items as they are
  * generated
  * @return True if any updates occurred (new synchronization, new mo_graph
@@ -1207,15 +1242,17 @@ void ModelChecker::get_release_seq_heads(ModelAction *act, rel_heads_list_t *rel
  */
 bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_queue)
 {
-       action_list_t *list;
-       list = lazy_sync_with_release->getptr(location);
-       if (!list)
-               return false;
-
        bool updated = false;
-       action_list_t::iterator it = list->begin();
-       while (it != list->end()) {
+       std::vector<ModelAction *>::iterator it = pending_acq_rel_seq->begin();
+       while (it != pending_acq_rel_seq->end()) {
                ModelAction *act = *it;
+
+               /* Only resolve sequences on the given location, if provided */
+               if (location && act->get_location() != location) {
+                       it++;
+                       continue;
+               }
+
                const ModelAction *rf = act->get_reads_from();
                rel_heads_list_t release_heads;
                bool complete;
@@ -1233,7 +1270,7 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_
 
                        /* propagate synchronization to later actions */
                        action_list_t::reverse_iterator it = action_trace->rbegin();
-                       while ((*it) != act) {
+                       for (; (*it) != act; it++) {
                                ModelAction *propagate = *it;
                                if (act->happens_before(propagate)) {
                                        propagate->synchronize_with(act);
@@ -1242,10 +1279,9 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_
                                }
                        }
                }
-               if (complete) {
-                       it = list->erase(it);
-                       (*lazy_sync_size)--;
-               } else
+               if (complete)
+                       it = pending_acq_rel_seq->erase(it);
+               else
                        it++;
        }
 
@@ -1545,7 +1581,6 @@ void ModelChecker::add_thread(Thread *t)
  * Removes a thread from the scheduler. 
  * @param the thread to remove.
  */
-
 void ModelChecker::remove_thread(Thread *t)
 {
        scheduler->remove_thread(t);