X-Git-Url: http://plrg.eecs.uci.edu/git/?p=model-checker.git;a=blobdiff_plain;f=model.cc;h=c713d1e1761b308cc5433dfa05345a8c23d5024a;hp=e0edbec091bcba052b926c560dec5a306fe8bee4;hb=664f78de4c437f087e5d0cf0b893a95cf7c50072;hpb=7064cf5c139946885b0d3df46f59da4946b34d4c diff --git a/model.cc b/model.cc index e0edbec..c713d1e 100644 --- a/model.cc +++ b/model.cc @@ -263,10 +263,6 @@ Thread * ModelChecker::get_next_thread(ModelAction *curr) /* The next node will read from a different value. */ tid = next->get_tid(); node_stack->pop_restofstack(2); - } else if (nextnode->increment_future_value()) { - /* The next node will try to read from a different future value. */ - tid = next->get_tid(); - node_stack->pop_restofstack(2); } else if (nextnode->increment_relseq_break()) { /* The next node will try to resolve a release sequence differently */ tid = next->get_tid(); @@ -282,6 +278,8 @@ Thread * ModelChecker::get_next_thread(ModelAction *curr) earliest_diverge = prevnode->get_action(); } } + /* Start the round robin scheduler from this thread id */ + scheduler->set_scheduler_thread(tid); /* The correct sleep set is in the parent node. */ execute_sleep_set(); @@ -313,14 +311,41 @@ void ModelChecker::execute_sleep_set() } } +/** + * @brief Should the current action wake up a given thread? + * + * @param curr The current action + * @param thread The thread that we might wake up + * @return True, if we should wake up the sleeping thread; false otherwise + */ +bool ModelChecker::should_wake_up(const ModelAction *curr, const Thread *thread) const +{ + const ModelAction *asleep = thread->get_pending(); + /* Don't allow partial RMW to wake anyone up */ + if (curr->is_rmwr()) + return false; + /* Synchronizing actions may have been backtracked */ + if (asleep->could_synchronize_with(curr)) + return true; + /* All acquire/release fences and fence-acquire/store-release */ + if (asleep->is_fence() && asleep->is_acquire() && curr->is_release()) + return true; + /* Fence-release + store can awake load-acquire on the same location */ + if (asleep->is_read() && asleep->is_acquire() && curr->same_var(asleep) && curr->is_write()) { + ModelAction *fence_release = get_last_fence_release(curr->get_tid()); + if (fence_release && *(get_last_action(thread->get_id())) < *fence_release) + return true; + } + return false; +} + void ModelChecker::wake_up_sleeping_actions(ModelAction *curr) { for (unsigned int i = 0; i < get_num_threads(); i++) { Thread *thr = get_thread(int_to_id(i)); if (scheduler->is_sleep_set(thr)) { - ModelAction *pending_act = thr->get_pending(); - if ((!curr->is_rmwr()) && pending_act->could_synchronize_with(curr)) - //Remove this thread from sleep set + if (should_wake_up(curr, thr)) + /* Remove this thread from sleep set */ scheduler->remove_sleep(thr); } } @@ -375,6 +400,22 @@ bool ModelChecker::is_deadlocked() const return blocking_threads; } +/** + * Check if a Thread has entered a circular wait deadlock situation. This will + * not check other threads for potential deadlock situations, and may miss + * deadlocks involving WAIT. + * + * @param t The thread which may have entered a deadlock + * @return True if this Thread entered a deadlock; false otherwise + */ +bool ModelChecker::is_circular_wait(const Thread *t) const +{ + for (Thread *waiting = t->waiting_on() ; waiting != NULL; waiting = waiting->waiting_on()) + if (waiting == t) + return true; + return false; +} + /** * Check if this is a complete execution. That is, have all thread completed * execution (rather than exiting because sleep sets have forced a redundant @@ -458,8 +499,16 @@ void ModelChecker::record_stats() stats.num_buggy_executions++; else if (is_complete_execution()) stats.num_complete++; - else + else { stats.num_redundant++; + + /** + * @todo We can violate this ASSERT() when fairness/sleep sets + * conflict to cause an execution to terminate, e.g. with: + * Scheduler: [0: disabled][1: disabled][2: sleep][3: current, enabled] + */ + //ASSERT(scheduler->all_threads_sleeping()); + } } /** @brief Print execution stats */ @@ -572,16 +621,22 @@ ModelAction * ModelChecker::get_last_fence_conflict(ModelAction *act) const if (!last_release) return NULL; - std::vector< ModelAction *, ModelAlloc > acquire_fences(get_num_threads(), NULL); - std::vector< ModelAction *, ModelAlloc > prior_loads(get_num_threads(), NULL); - bool found_acquire_fences = false; + /* Skip past the release */ + action_list_t *list = action_trace; + action_list_t::reverse_iterator rit; + for (rit = list->rbegin(); rit != list->rend(); rit++) + if (*rit == last_release) + break; + ASSERT(rit != list->rend()); + /* Find a prior: * load-acquire * or * load --sb-> fence-acquire */ - action_list_t *list = action_trace; - action_list_t::reverse_iterator rit; - for (rit = list->rbegin(); rit != list->rend(); rit++) { + std::vector< ModelAction *, ModelAlloc > acquire_fences(get_num_threads(), NULL); + std::vector< ModelAction *, ModelAlloc > prior_loads(get_num_threads(), NULL); + bool found_acquire_fences = false; + for ( ; rit != list->rend(); rit++) { ModelAction *prev = *rit; if (act->same_thread(prev)) continue; @@ -807,51 +862,65 @@ ModelAction * ModelChecker::get_next_backtrack() } /** - * Processes a read or rmw model action. + * Processes a read model action. * @param curr is the read model action to process. - * @param second_part_of_rmw is boolean that is true is this is the second action of a rmw. * @return True if processing this read updates the mo_graph. */ -bool ModelChecker::process_read(ModelAction *curr, bool second_part_of_rmw) +bool ModelChecker::process_read(ModelAction *curr) { - uint64_t value = VALUE_NONE; - bool updated = false; + Node *node = curr->get_node(); while (true) { - const ModelAction *reads_from = curr->get_node()->get_read_from(); - if (reads_from != NULL) { - mo_graph->startChanges(); + bool updated = false; + switch (node->get_read_from_status()) { + case READ_FROM_PAST: { + const ModelAction *rf = node->get_read_from_past(); + ASSERT(rf); - value = reads_from->get_value(); - bool r_status = false; - - if (!second_part_of_rmw) { - check_recency(curr, reads_from); - r_status = r_modification_order(curr, reads_from); - } + mo_graph->startChanges(); - if (!second_part_of_rmw && is_infeasible() && (curr->get_node()->increment_read_from() || curr->get_node()->increment_future_value())) { - mo_graph->rollbackChanges(); - priv->too_many_reads = false; - continue; + ASSERT(!is_infeasible()); + if (!check_recency(curr, rf)) { + if (node->increment_read_from()) { + mo_graph->rollbackChanges(); + continue; + } else { + priv->too_many_reads = true; + } } - read_from(curr, reads_from); + updated = r_modification_order(curr, rf); + read_from(curr, rf); mo_graph->commitChanges(); mo_check_promises(curr, true); - - updated |= r_status; - } else if (!second_part_of_rmw) { + break; + } + case READ_FROM_PROMISE: { + Promise *promise = curr->get_node()->get_read_from_promise(); + if (promise->add_reader(curr)) + priv->failed_promise = true; + curr->set_read_from_promise(promise); + mo_graph->startChanges(); + if (!check_recency(curr, promise)) + priv->too_many_reads = true; + updated = r_modification_order(curr, promise); + mo_graph->commitChanges(); + break; + } + case READ_FROM_FUTURE: { /* Read from future value */ - struct future_value fv = curr->get_node()->get_future_value(); + struct future_value fv = node->get_future_value(); Promise *promise = new Promise(curr, fv); - value = fv.value; curr->set_read_from_promise(promise); promises->push_back(promise); mo_graph->startChanges(); updated = r_modification_order(curr, promise); mo_graph->commitChanges(); + break; + } + default: + ASSERT(false); } - get_thread(curr)->set_return_value(value); + get_thread(curr)->set_return_value(curr->get_return_value()); return updated; } } @@ -874,20 +943,15 @@ bool ModelChecker::process_read(ModelAction *curr, bool second_part_of_rmw) */ bool ModelChecker::process_mutex(ModelAction *curr) { - std::mutex *mutex = NULL; + std::mutex *mutex = curr->get_mutex(); struct std::mutex_state *state = NULL; - if (curr->is_trylock() || curr->is_lock() || curr->is_unlock()) { - mutex = (std::mutex *)curr->get_location(); - state = mutex->get_state(); - } else if (curr->is_wait()) { - mutex = (std::mutex *)curr->get_value(); + if (mutex) state = mutex->get_state(); - } switch (curr->get_type()) { case ATOMIC_TRYLOCK: { - bool success = !state->islocked; + bool success = !state->locked; curr->set_try_lock(success); if (!success) { get_thread(curr)->set_return_value(0); @@ -899,7 +963,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) case ATOMIC_LOCK: { if (curr->get_cv()->getClock(state->alloc_tid) <= state->alloc_clock) assert_bug("Lock access before initialization"); - state->islocked = true; + state->locked = get_thread(curr); ModelAction *unlock = get_last_unlock(curr); //synchronize with the previous unlock statement if (unlock != NULL) { @@ -910,7 +974,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) } case ATOMIC_UNLOCK: { //unlock the lock - state->islocked = false; + state->locked = NULL; //wake up the other threads action_list_t *waiters = get_safe_ptr_action(lock_waiters_map, curr->get_location()); //activate all the waiting threads @@ -922,7 +986,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) } case ATOMIC_WAIT: { //unlock the lock - state->islocked = false; + state->locked = NULL; //wake up the other threads action_list_t *waiters = get_safe_ptr_action(lock_waiters_map, (void *) curr->get_value()); //activate all the waiting threads @@ -975,7 +1039,7 @@ void ModelChecker::add_future_value(const ModelAction *writer, ModelAction *read write_thread = write_thread->get_parent(); struct future_value fv = { - writer->get_value(), + writer->get_write_value(), writer->get_seq_number() + params.maxfuturedelay, write_thread->get_id(), }; @@ -991,8 +1055,26 @@ void ModelChecker::add_future_value(const ModelAction *writer, ModelAction *read */ bool ModelChecker::process_write(ModelAction *curr) { - bool updated_mod_order = w_modification_order(curr); - bool updated_promises = resolve_promises(curr); + /* Readers to which we may send our future value */ + std::vector< ModelAction *, ModelAlloc > send_fv; + + bool updated_mod_order = w_modification_order(curr, &send_fv); + int promise_idx = get_promise_to_resolve(curr); + const ModelAction *earliest_promise_reader; + bool updated_promises = false; + + if (promise_idx >= 0) { + earliest_promise_reader = (*promises)[promise_idx]->get_reader(0); + updated_promises = resolve_promise(curr, promise_idx); + } else + earliest_promise_reader = NULL; + + /* Don't send future values to reads after the Promise we resolve */ + for (unsigned int i = 0; i < send_fv.size(); i++) { + ModelAction *read = send_fv[i]; + if (!earliest_promise_reader || *read < *earliest_promise_reader) + futurevalues->push_back(PendingFutureValue(curr, read)); + } if (promises->size() == 0) { for (unsigned int i = 0; i < futurevalues->size(); i++) { @@ -1078,7 +1160,7 @@ bool ModelChecker::process_thread_action(ModelAction *curr) case THREAD_CREATE: { thrd_t *thrd = (thrd_t *)curr->get_location(); struct thread_params *params = (struct thread_params *)curr->get_value(); - Thread *th = new Thread(thrd, params->func, params->arg); + Thread *th = new Thread(thrd, params->func, params->arg, get_thread(curr)); add_thread(th); th->set_creation(curr); /* Promises can be satisfied by children */ @@ -1277,8 +1359,9 @@ bool ModelChecker::initialize_curr_action(ModelAction **curr) */ bool ModelChecker::read_from(ModelAction *act, const ModelAction *rf) { + ASSERT(rf); act->set_read_from(rf); - if (rf != NULL && act->is_acquire()) { + if (act->is_acquire()) { rel_heads_list_t release_heads; get_release_seq_heads(act, act, &release_heads); int num_heads = release_heads.size(); @@ -1292,6 +1375,35 @@ bool ModelChecker::read_from(ModelAction *act, const ModelAction *rf) return false; } +/** + * Check promises and eliminate potentially-satisfying threads when a thread is + * blocked (e.g., join, lock). A thread which is waiting on another thread can + * no longer satisfy a promise generated from that thread. + * + * @param blocker The thread on which a thread is waiting + * @param waiting The waiting thread + */ +void ModelChecker::thread_blocking_check_promises(Thread *blocker, Thread *waiting) +{ + for (unsigned int i = 0; i < promises->size(); i++) { + Promise *promise = (*promises)[i]; + if (!promise->thread_is_available(waiting->get_id())) + continue; + for (unsigned int j = 0; j < promise->get_num_readers(); j++) { + ModelAction *reader = promise->get_reader(j); + if (reader->get_tid() != blocker->get_id()) + continue; + if (promise->eliminate_thread(waiting->get_id())) { + /* Promise has failed */ + priv->failed_promise = true; + } else { + /* Only eliminate the 'waiting' thread once */ + return; + } + } + } +} + /** * @brief Check whether a model action is enabled. * @@ -1306,7 +1418,7 @@ bool ModelChecker::check_action_enabled(ModelAction *curr) { if (curr->is_lock()) { std::mutex *lock = (std::mutex *)curr->get_location(); struct std::mutex_state *state = lock->get_state(); - if (state->islocked) { + if (state->locked) { //Stick the action in the appropriate waiting queue get_safe_ptr_action(lock_waiters_map, curr->get_location())->push_back(curr); return false; @@ -1315,6 +1427,7 @@ bool ModelChecker::check_action_enabled(ModelAction *curr) { Thread *blocking = (Thread *)curr->get_location(); if (!blocking->is_complete()) { blocking->push_wait_list(curr); + thread_blocking_check_promises(blocking, get_thread(curr)); return false; } } @@ -1360,7 +1473,7 @@ ModelAction * ModelChecker::check_current_action(ModelAction *curr) /* Build may_read_from set for newly-created actions */ if (newly_explored && curr->is_read()) - build_reads_from_past(curr); + build_may_read_from(curr); /* Initialize work_queue with the "current action" work */ work_queue_t work_queue(1, CheckCurrWorkEntry(curr)); @@ -1377,7 +1490,7 @@ ModelAction * ModelChecker::check_current_action(ModelAction *curr) if (process_thread_action(curr)) update_all = true; - if (act->is_read() && process_read(act, second_part_of_rmw)) + if (act->is_read() && !second_part_of_rmw && process_read(act)) update = true; if (act->is_write() && process_write(act)) @@ -1418,7 +1531,7 @@ ModelAction * ModelChecker::check_current_action(ModelAction *curr) } } if (act->is_write()) { - if (w_modification_order(act)) + if (w_modification_order(act, NULL)) updated = true; } mo_graph->commitChanges(); @@ -1446,7 +1559,6 @@ void ModelChecker::check_curr_backtracking(ModelAction *curr) if ((parnode && !parnode->backtrack_empty()) || !currnode->misc_empty() || !currnode->read_from_empty() || - !currnode->future_value_empty() || !currnode->promise_empty() || !currnode->relseq_break_empty()) { set_latest_backtrack(curr); @@ -1540,92 +1652,106 @@ ModelAction * ModelChecker::process_rmw(ModelAction *act) { } /** - * Checks whether a thread has read from the same write for too many times - * without seeing the effects of a later write. - * - * Basic idea: - * 1) there must a different write that we could read from that would satisfy the modification order, - * 2) we must have read from the same value in excess of maxreads times, and - * 3) that other write must have been in the reads_from set for maxreads times. + * A helper function for ModelChecker::check_recency, to check if the current + * thread is able to read from a different write/promise for 'params.maxreads' + * number of steps and if that write/promise should become visible (i.e., is + * ordered later in the modification order). This helps model memory liveness. * - * If so, we decide that the execution is no longer feasible. + * @param curr The current action. Must be a read. + * @param rf The write/promise from which we plan to read + * @param other_rf The write/promise from which we may read + * @return True if we were able to read from other_rf for params.maxreads steps */ -void ModelChecker::check_recency(ModelAction *curr, const ModelAction *rf) +template +bool ModelChecker::should_read_instead(const ModelAction *curr, const T *rf, const U *other_rf) const { - if (params.maxreads != 0) { - if (curr->get_node()->get_read_from_size() <= 1) - return; - //Must make sure that execution is currently feasible... We could - //accidentally clear by rolling back - if (is_infeasible()) - return; - std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); - int tid = id_to_int(curr->get_tid()); - - /* Skip checks */ - if ((int)thrd_lists->size() <= tid) - return; - action_list_t *list = &(*thrd_lists)[tid]; - - action_list_t::reverse_iterator rit = list->rbegin(); - /* Skip past curr */ - for (; (*rit) != curr; rit++) - ; - /* go past curr now */ - rit++; - - action_list_t::reverse_iterator ritcopy = rit; - //See if we have enough reads from the same value - int count = 0; - for (; count < params.maxreads; rit++, count++) { - if (rit == list->rend()) - return; - ModelAction *act = *rit; - if (!act->is_read()) - return; - - if (act->get_reads_from() != rf) - return; - if (act->get_node()->get_read_from_size() <= 1) - return; - } - for (int i = 0; i < curr->get_node()->get_read_from_size(); i++) { - /* Get write */ - const ModelAction *write = curr->get_node()->get_read_from_at(i); + /* Need a different write/promise */ + if (other_rf->equals(rf)) + return false; - /* Need a different write */ - if (write == rf) - continue; + /* Only look for "newer" writes/promises */ + if (!mo_graph->checkReachable(rf, other_rf)) + return false; - /* Test to see whether this is a feasible write to read from */ - /** NOTE: all members of read-from set should be - * feasible, so we no longer check it here **/ + std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); + action_list_t *list = &(*thrd_lists)[id_to_int(curr->get_tid())]; + action_list_t::reverse_iterator rit = list->rbegin(); + ASSERT((*rit) == curr); + /* Skip past curr */ + rit++; + + /* Does this write/promise work for everyone? */ + for (int i = 0; i < params.maxreads; i++, rit++) { + ModelAction *act = *rit; + if (!act->may_read_from(other_rf)) + return false; + } + return true; +} - rit = ritcopy; +/** + * Checks whether a thread has read from the same write or Promise for too many + * times without seeing the effects of a later write/Promise. + * + * Basic idea: + * 1) there must a different write/promise that we could read from, + * 2) we must have read from the same write/promise in excess of maxreads times, + * 3) that other write/promise must have been in the reads_from set for maxreads times, and + * 4) that other write/promise must be mod-ordered after the write/promise we are reading. + * + * If so, we decide that the execution is no longer feasible. + * + * @param curr The current action. Must be a read. + * @param rf The ModelAction/Promise from which we might read. + * @return True if the read should succeed; false otherwise + */ +template +bool ModelChecker::check_recency(ModelAction *curr, const T *rf) const +{ + if (!params.maxreads) + return true; - bool feasiblewrite = true; - //new we need to see if this write works for everyone + //NOTE: Next check is just optimization, not really necessary.... + if (curr->get_node()->get_read_from_past_size() + + curr->get_node()->get_read_from_promise_size() <= 1) + return true; - for (int loop = count; loop > 0; loop--, rit++) { - ModelAction *act = *rit; - bool foundvalue = false; - for (int j = 0; j < act->get_node()->get_read_from_size(); j++) { - if (act->get_node()->get_read_from_at(j) == write) { - foundvalue = true; - break; - } - } - if (!foundvalue) { - feasiblewrite = false; - break; - } - } - if (feasiblewrite) { - priv->too_many_reads = true; - return; - } - } + std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); + int tid = id_to_int(curr->get_tid()); + ASSERT(tid < (int)thrd_lists->size()); + action_list_t *list = &(*thrd_lists)[tid]; + action_list_t::reverse_iterator rit = list->rbegin(); + ASSERT((*rit) == curr); + /* Skip past curr */ + rit++; + + action_list_t::reverse_iterator ritcopy = rit; + /* See if we have enough reads from the same value */ + for (int count = 0; count < params.maxreads; ritcopy++, count++) { + if (ritcopy == list->rend()) + return true; + ModelAction *act = *ritcopy; + if (!act->is_read()) + return true; + if (act->get_reads_from_promise() && !act->get_reads_from_promise()->equals(rf)) + return true; + if (act->get_reads_from() && !act->get_reads_from()->equals(rf)) + return true; + if (act->get_node()->get_read_from_past_size() + + act->get_node()->get_read_from_promise_size() <= 1) + return true; } + for (int i = 0; i < curr->get_node()->get_read_from_past_size(); i++) { + const ModelAction *write = curr->get_node()->get_read_from_past(i); + if (should_read_instead(curr, rf, write)) + return false; /* liveness failure */ + } + for (int i = 0; i < curr->get_node()->get_read_from_promise_size(); i++) { + const Promise *promise = curr->get_node()->get_read_from_promise(i); + if (should_read_instead(curr, rf, promise)) + return false; /* liveness failure */ + } + return true; } /** @@ -1705,13 +1831,13 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const rf_type *rf) added = mo_graph->addEdge(act, rf) || added; } } else { - const ModelAction *prevreadfrom = act->get_reads_from(); - //if the previous read is unresolved, keep going... - if (prevreadfrom == NULL) - continue; - - if (!prevreadfrom->equals(rf)) { - added = mo_graph->addEdge(prevreadfrom, rf) || added; + const ModelAction *prevrf = act->get_reads_from(); + const Promise *prevrf_promise = act->get_reads_from_promise(); + if (prevrf) { + if (!prevrf->equals(rf)) + added = mo_graph->addEdge(prevrf, rf) || added; + } else if (!prevrf_promise->equals(rf)) { + added = mo_graph->addEdge(prevrf_promise, rf) || added; } } break; @@ -1750,9 +1876,11 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const rf_type *rf) * (II) Sending the write back to non-synchronizing reads. * * @param curr The current action. Must be a write. + * @param send_fv A vector for stashing reads to which we may pass our future + * value. If NULL, then don't record any future values. * @return True if modification order edges were added; false otherwise */ -bool ModelChecker::w_modification_order(ModelAction *curr) +bool ModelChecker::w_modification_order(ModelAction *curr, std::vector< ModelAction *, ModelAlloc > *send_fv) { std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); unsigned int i; @@ -1845,9 +1973,9 @@ bool ModelChecker::w_modification_order(ModelAction *curr) pendingfuturevalue. */ - if (thin_air_constraint_may_allow(curr, act)) { + if (send_fv && thin_air_constraint_may_allow(curr, act)) { if (!is_infeasible()) - futurevalues->push_back(PendingFutureValue(curr, act)); + send_fv->push_back(act); else if (curr->is_rmw() && act->is_rmw() && curr->get_reads_from() && curr->get_reads_from() == act->get_reads_from()) add_future_value(curr, act); } @@ -2369,51 +2497,60 @@ ClockVector * ModelChecker::get_cv(thread_id_t tid) const } /** - * Resolve a set of Promises with a current write. The set is provided in the - * Node corresponding to @a write. + * @brief Find the promise, if any to resolve for the current action + * @param curr The current ModelAction. Should be a write. + * @return The (non-negative) index for the Promise to resolve, if any; + * otherwise -1 + */ +int ModelChecker::get_promise_to_resolve(const ModelAction *curr) const +{ + for (unsigned int i = 0; i < promises->size(); i++) + if (curr->get_node()->get_promise(i)) + return i; + return -1; +} + +/** + * Resolve a Promise with a current write. * @param write The ModelAction that is fulfilling Promises - * @return True if promises were resolved; false otherwise + * @param promise_idx The index corresponding to the promise + * @return True if the Promise was successfully resolved; false otherwise */ -bool ModelChecker::resolve_promises(ModelAction *write) +bool ModelChecker::resolve_promise(ModelAction *write, unsigned int promise_idx) { - bool haveResolved = false; std::vector< ModelAction *, ModelAlloc > actions_to_check; - promise_list_t mustResolve, resolved; - - for (unsigned int i = 0, promise_index = 0; promise_index < promises->size(); i++) { - Promise *promise = (*promises)[promise_index]; - if (write->get_node()->get_promise(i)) { - ModelAction *read = promise->get_action(); - read_from(read, write); - //Make sure the promise's value matches the write's value - ASSERT(promise->is_compatible(write)); - mo_graph->resolvePromise(read, write, &mustResolve); - - resolved.push_back(promise); - promises->erase(promises->begin() + promise_index); - actions_to_check.push_back(read); + Promise *promise = (*promises)[promise_idx]; - haveResolved = true; - } else - promise_index++; + for (unsigned int i = 0; i < promise->get_num_readers(); i++) { + ModelAction *read = promise->get_reader(i); + read_from(read, write); + actions_to_check.push_back(read); } + /* Make sure the promise's value matches the write's value */ + ASSERT(promise->is_compatible(write) && promise->same_value(write)); + if (!mo_graph->resolvePromise(promise, write)) + priv->failed_promise = true; + + promises->erase(promises->begin() + promise_idx); + /** + * @todo It is possible to end up in an inconsistent state, where a + * "resolved" promise may still be referenced if + * CycleGraph::resolvePromise() failed, so don't delete 'promise'. + * + * Note that the inconsistency only matters when dumping mo_graph to + * file. + * + * delete promise; + */ - for (unsigned int i = 0; i < mustResolve.size(); i++) { - if (std::find(resolved.begin(), resolved.end(), mustResolve[i]) - == resolved.end()) - priv->failed_promise = true; - } - for (unsigned int i = 0; i < resolved.size(); i++) - delete resolved[i]; //Check whether reading these writes has made threads unable to //resolve promises - for (unsigned int i = 0; i < actions_to_check.size(); i++) { ModelAction *read = actions_to_check[i]; mo_check_promises(read, true); } - return haveResolved; + return true; } /** @@ -2426,15 +2563,20 @@ void ModelChecker::compute_promises(ModelAction *curr) { for (unsigned int i = 0; i < promises->size(); i++) { Promise *promise = (*promises)[i]; - const ModelAction *act = promise->get_action(); - if (!act->happens_before(curr) && - act->is_read() && - !act->could_synchronize_with(curr) && - !act->same_thread(curr) && - act->get_location() == curr->get_location() && - promise->get_value() == curr->get_value()) { - curr->get_node()->set_promise(i, act->is_rmw()); + if (!promise->is_compatible(curr) || !promise->same_value(curr)) + continue; + + bool satisfy = true; + for (unsigned int j = 0; j < promise->get_num_readers(); j++) { + const ModelAction *act = promise->get_reader(j); + if (act->happens_before(curr) || + act->could_synchronize_with(curr)) { + satisfy = false; + break; + } } + if (satisfy) + curr->get_node()->set_promise(i); } } @@ -2443,13 +2585,17 @@ void ModelChecker::check_promises(thread_id_t tid, ClockVector *old_cv, ClockVec { for (unsigned int i = 0; i < promises->size(); i++) { Promise *promise = (*promises)[i]; - const ModelAction *act = promise->get_action(); - if ((old_cv == NULL || !old_cv->synchronized_since(act)) && - merge_cv->synchronized_since(act)) { - if (promise->eliminate_thread(tid)) { - //Promise has failed - priv->failed_promise = true; - return; + if (!promise->thread_is_available(tid)) + continue; + for (unsigned int j = 0; j < promise->get_num_readers(); j++) { + const ModelAction *act = promise->get_reader(j); + if ((!old_cv || !old_cv->synchronized_since(act)) && + merge_cv->synchronized_since(act)) { + if (promise->eliminate_thread(tid)) { + /* Promise has failed */ + priv->failed_promise = true; + return; + } } } } @@ -2486,15 +2632,20 @@ void ModelChecker::mo_check_promises(const ModelAction *act, bool is_read_check) for (unsigned int i = 0; i < promises->size(); i++) { Promise *promise = (*promises)[i]; - const ModelAction *pread = promise->get_action(); // Is this promise on the same location? - if (!pread->same_var(write)) + if (!promise->same_location(write)) continue; - if (pread->happens_before(act) && mo_graph->checkPromise(write, promise)) { - priv->failed_promise = true; - return; + for (unsigned int j = 0; j < promise->get_num_readers(); j++) { + const ModelAction *pread = promise->get_reader(j); + if (!pread->happens_before(act)) + continue; + if (mo_graph->checkPromise(write, promise)) { + priv->failed_promise = true; + return; + } + break; } // Don't do any lookups twice for the same thread @@ -2535,12 +2686,12 @@ void ModelChecker::compute_relseq_breakwrites(ModelAction *curr) /** * Build up an initial set of all past writes that this 'read' action may read - * from. This set is determined by the clock vector's "happens before" - * relationship. + * from, as well as any previously-observed future values that must still be valid. + * * @param curr is the current ModelAction that we are exploring; it must be a * 'read' operation. */ -void ModelChecker::build_reads_from_past(ModelAction *curr) +void ModelChecker::build_may_read_from(ModelAction *curr) { std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); unsigned int i; @@ -2576,7 +2727,7 @@ void ModelChecker::build_reads_from_past(ModelAction *curr) mo_graph->startChanges(); r_modification_order(curr, act); if (!is_infeasible()) - curr->get_node()->add_read_from(act); + curr->get_node()->add_read_from_past(act); mo_graph->rollbackChanges(); } @@ -2585,8 +2736,23 @@ void ModelChecker::build_reads_from_past(ModelAction *curr) break; } } + + /* Inherit existing, promised future values */ + for (i = 0; i < promises->size(); i++) { + const Promise *promise = (*promises)[i]; + const ModelAction *promise_read = promise->get_reader(0); + if (promise_read->same_var(curr)) { + /* Only add feasible future-values */ + mo_graph->startChanges(); + r_modification_order(curr, promise); + if (!is_infeasible()) + curr->get_node()->add_read_from_promise(promise_read); + mo_graph->rollbackChanges(); + } + } + /* We may find no valid may-read-from only if the execution is doomed */ - if (!curr->get_node()->get_read_from_size()) { + if (!curr->get_node()->read_from_size()) { priv->no_valid_reads = true; set_assert(); } @@ -2594,9 +2760,9 @@ void ModelChecker::build_reads_from_past(ModelAction *curr) if (DBG_ENABLED()) { model_print("Reached read action:\n"); curr->print(); - model_print("Printing may_read_from\n"); - curr->get_node()->print_may_read_from(); - model_print("End printing may_read_from\n"); + model_print("Printing read_from_past\n"); + curr->get_node()->print_read_from_past(); + model_print("End printing read_from_past\n"); } } @@ -2657,17 +2823,28 @@ void ModelChecker::dumpGraph(char *filename) const ModelAction **thread_array = (ModelAction **)model_calloc(1, sizeof(ModelAction *) * get_num_threads()); for (action_list_t::iterator it = action_trace->begin(); it != action_trace->end(); it++) { - ModelAction *action = *it; - if (action->is_read()) { - fprintf(file, "N%u [label=\"N%u, T%u\"];\n", action->get_seq_number(), action->get_seq_number(), action->get_tid()); - if (action->get_reads_from() != NULL) - fprintf(file, "N%u -> N%u[label=\"rf\", color=red];\n", action->get_seq_number(), action->get_reads_from()->get_seq_number()); + ModelAction *act = *it; + if (act->is_read()) { + mo_graph->dot_print_node(file, act); + if (act->get_reads_from()) + mo_graph->dot_print_edge(file, + act->get_reads_from(), + act, + "label=\"rf\", color=red, weight=2"); + else + mo_graph->dot_print_edge(file, + act->get_reads_from_promise(), + act, + "label=\"rf\", color=red"); } - if (thread_array[action->get_tid()] != NULL) { - fprintf(file, "N%u -> N%u[label=\"sb\", color=blue];\n", thread_array[action->get_tid()]->get_seq_number(), action->get_seq_number()); + if (thread_array[act->get_tid()]) { + mo_graph->dot_print_edge(file, + thread_array[id_to_int(act->get_tid())], + act, + "label=\"sb\", color=blue, weight=400"); } - thread_array[action->get_tid()] = action; + thread_array[act->get_tid()] = act; } fprintf(file, "}\n"); model_free(thread_array); @@ -2687,9 +2864,11 @@ void ModelChecker::print_summary() const #endif model_print("Execution %d:", stats.num_total); - if (isfeasibleprefix()) + if (isfeasibleprefix()) { + if (scheduler->all_threads_sleeping()) + model_print(" SLEEP-SET REDUNDANT"); model_print("\n"); - else + } else print_infeasibility(" INFEASIBLE"); print_list(action_trace); model_print("\n"); @@ -2735,6 +2914,26 @@ Thread * ModelChecker::get_thread(const ModelAction *act) const return get_thread(act->get_tid()); } +/** + * @brief Get a Promise's "promise number" + * + * A "promise number" is an index number that is unique to a promise, valid + * only for a specific snapshot of an execution trace. Promises may come and go + * as they are generated an resolved, so an index only retains meaning for the + * current snapshot. + * + * @param promise The Promise to check + * @return The promise index, if the promise still is valid; otherwise -1 + */ +int ModelChecker::get_promise_number(const Promise *promise) const +{ + for (unsigned int i = 0; i < promises->size(); i++) + if ((*promises)[i] == promise) + return i; + /* Not found */ + return -1; +} + /** * @brief Check if a Thread is currently enabled * @param t The Thread to check @@ -2838,7 +3037,7 @@ void ModelChecker::run() { do { thrd_t user_thread; - Thread *t = new Thread(&user_thread, &user_main_wrapper, NULL); + Thread *t = new Thread(&user_thread, &user_main_wrapper, NULL, NULL); add_thread(t); do { @@ -2853,6 +3052,8 @@ void ModelChecker::run() Thread *thr = get_thread(tid); if (!thr->is_model_thread() && !thr->is_complete() && !thr->get_pending()) { switch_from_master(thr); + if (is_circular_wait(thr)) + assert_bug("Deadlock detected"); } }