X-Git-Url: http://plrg.eecs.uci.edu/git/?p=model-checker.git;a=blobdiff_plain;f=model.cc;h=afb72b0aae490bda4a0464bb04837b380d81f4ca;hp=7048096ca25e0cbe0ad9928504802de4231c5a18;hb=07b041c2dd6958bb3a52ffcba07e8e642130548c;hpb=875ebf8e11b4bdd702604785837b6b91b748900d diff --git a/model.cc b/model.cc index 7048096..afb72b0 100644 --- a/model.cc +++ b/model.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include "model.h" #include "action.h" @@ -15,25 +16,13 @@ #include "datarace.h" #include "threads-model.h" #include "output.h" +#include "traceanalysis.h" +#include "bugmessage.h" #define INITIAL_THREAD_ID 0 ModelChecker *model; -struct bug_message { - bug_message(const char *str) { - const char *fmt = " [BUG] %s\n"; - msg = (char *)snapshot_malloc(strlen(fmt) + strlen(str)); - sprintf(msg, fmt, str); - } - ~bug_message() { if (msg) snapshot_free(msg); } - - char *msg; - void print() { model_print("%s", msg); } - - SNAPSHOTALLOC -}; - /** * Structure for holding small ModelChecker members that should be snapshotted */ @@ -61,7 +50,7 @@ struct model_snapshot_members { unsigned int next_thread_id; modelclock_t used_sequence_numbers; ModelAction *next_backtrack; - std::vector< bug_message *, SnapshotAlloc > bugs; + SnapVector bugs; struct execution_stats stats; bool failed_promise; bool too_many_reads; @@ -83,15 +72,15 @@ ModelChecker::ModelChecker(struct model_params params) : action_trace(new action_list_t()), thread_map(new HashTable()), obj_map(new HashTable()), - lock_waiters_map(new HashTable()), condvar_waiters_map(new HashTable()), - obj_thrd_map(new HashTable *, uintptr_t, 4 >()), - promises(new std::vector< Promise *, SnapshotAlloc >()), - futurevalues(new std::vector< struct PendingFutureValue, SnapshotAlloc >()), - pending_rel_seqs(new std::vector< struct release_seq *, SnapshotAlloc >()), - thrd_last_action(new std::vector< ModelAction *, SnapshotAlloc >(1)), - thrd_last_fence_release(new std::vector< ModelAction *, SnapshotAlloc >()), + obj_thrd_map(new HashTable *, uintptr_t, 4 >()), + promises(new SnapVector()), + futurevalues(new SnapVector()), + pending_rel_seqs(new SnapVector()), + thrd_last_action(new SnapVector(1)), + thrd_last_fence_release(new SnapVector()), node_stack(new NodeStack()), + trace_analyses(new ModelVector()), priv(new struct model_snapshot_members()), mo_graph(new CycleGraph()) { @@ -109,7 +98,6 @@ ModelChecker::~ModelChecker() delete obj_thrd_map; delete obj_map; - delete lock_waiters_map; delete condvar_waiters_map; delete action_trace; @@ -122,6 +110,9 @@ ModelChecker::~ModelChecker() delete thrd_last_action; delete thrd_last_fence_release; delete node_stack; + for (unsigned int i = 0; i < trace_analyses->size(); i++) + delete (*trace_analyses)[i]; + delete trace_analyses; delete scheduler; delete mo_graph; delete priv; @@ -137,16 +128,28 @@ static action_list_t * get_safe_ptr_action(HashTable * get_safe_ptr_vect_action(HashTable *, uintptr_t, 4> * hash, void * ptr) +static SnapVector * get_safe_ptr_vect_action(HashTable *, uintptr_t, 4> * hash, void * ptr) { - std::vector *tmp = hash->get(ptr); + SnapVector *tmp = hash->get(ptr); if (tmp == NULL) { - tmp = new std::vector(); + tmp = new SnapVector(); hash->put(ptr, tmp); } return tmp; } +action_list_t * ModelChecker::get_actions_on_obj(void * obj, thread_id_t tid) { + SnapVector *wrv=obj_thrd_map->get(obj); + if (wrv==NULL) + return NULL; + unsigned int thread=id_to_int(tid); + if (thread < wrv->size()) + return &(*wrv)[thread]; + else + return NULL; +} + + /** * Restores user program to initial state and resets all model-checker data * structures. @@ -156,9 +159,6 @@ void ModelChecker::reset_to_initial_state() DEBUG("+++ Resetting to initial state +++\n"); node_stack->reset_execution(); - /* Print all model-checker output before rollback */ - fflush(model_out); - /** * FIXME: if we utilize partial rollback, we will need to free only * those pending actions which were NOT pending before the rollback @@ -204,34 +204,43 @@ Node * ModelChecker::get_curr_node() const return node_stack->get_head(); } +/** + * @brief Select the next thread to execute based on the curren action + * + * RMW actions occur in two parts, and we cannot split them. And THREAD_CREATE + * actions should be followed by the execution of their child thread. In either + * case, the current action should determine the next thread schedule. + * + * @param curr The current action + * @return The next thread to run, if the current action will determine this + * selection; otherwise NULL + */ +Thread * ModelChecker::action_select_next_thread(const ModelAction *curr) const +{ + /* Do not split atomic RMW */ + if (curr->is_rmwr()) + return get_thread(curr); + /* Follow CREATE with the created thread */ + if (curr->get_type() == THREAD_CREATE) + return curr->get_thread_operand(); + return NULL; +} + /** * @brief Choose the next thread to execute. * - * This function chooses the next thread that should execute. It can force the - * adjacency of read/write portions of a RMW action, force THREAD_CREATE to be - * followed by a THREAD_START, or it can enforce execution replay/backtracking. - * The model-checker may have no preference regarding the next thread (i.e., - * when exploring a new execution ordering), in which case we defer to the - * scheduler. + * This function chooses the next thread that should execute. It can enforce + * execution replay/backtracking or, if the model-checker has no preference + * regarding the next thread (i.e., when exploring a new execution ordering), + * we defer to the scheduler. * - * @param curr Optional: The current ModelAction. Only used if non-NULL and it - * might guide the choice of next thread (i.e., THREAD_CREATE should be - * followed by THREAD_START, or ATOMIC_RMWR followed by ATOMIC_{RMW,RMWC}) - * @return The next chosen thread to run, if any exist. Or else if no threads - * remain to be executed, return NULL. + * @return The next chosen thread to run, if any exist. Or else if the current + * execution should terminate, return NULL. */ -Thread * ModelChecker::get_next_thread(ModelAction *curr) +Thread * ModelChecker::get_next_thread() { thread_id_t tid; - if (curr != NULL) { - /* Do not split atomic actions. */ - if (curr->is_rmwr()) - return get_thread(curr); - else if (curr->get_type() == THREAD_CREATE) - return curr->get_thread_operand(); - } - /* * Have we completed exploring the preselected path? Then let the * scheduler decide @@ -251,20 +260,8 @@ Thread * ModelChecker::get_next_thread(ModelAction *curr) scheduler->update_sleep_set(prevnode); /* Reached divergence point */ - if (nextnode->increment_misc()) { - /* The next node will try to satisfy a different misc_index values. */ - tid = next->get_tid(); - node_stack->pop_restofstack(2); - } else if (nextnode->increment_promise()) { - /* The next node will try to satisfy a different set of promises. */ - tid = next->get_tid(); - node_stack->pop_restofstack(2); - } else if (nextnode->increment_read_from()) { - /* The next node will read from a different 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 */ + if (nextnode->increment_behaviors()) { + /* Execute the same thread with a new behavior */ tid = next->get_tid(); node_stack->pop_restofstack(2); } else { @@ -291,7 +288,7 @@ Thread * ModelChecker::get_next_thread(ModelAction *curr) } DEBUG("*** ModelChecker chose next thread = %d ***\n", id_to_int(tid)); ASSERT(tid != THREAD_ID_T_NONE); - return thread_map->get(id_to_int(tid)); + return get_thread(id_to_int(tid)); } /** @@ -427,9 +424,16 @@ bool ModelChecker::is_complete_execution() const * @param msg Descriptive message for the bug (do not include newline char) * @return True if bug is immediately-feasible */ -bool ModelChecker::assert_bug(const char *msg) +bool ModelChecker::assert_bug(const char *msg, ...) { - priv->bugs.push_back(new bug_message(msg)); + char str[800]; + + va_list ap; + va_start(ap, msg); + vsnprintf(str, sizeof(str), msg, ap); + va_end(ap); + + priv->bugs.push_back(new bug_message(str)); if (isfeasibleprefix()) { set_assert(); @@ -483,10 +487,16 @@ void ModelChecker::record_stats() stats.num_buggy_executions++; else if (is_complete_execution()) stats.num_complete++; - else if (scheduler->all_threads_sleeping()) + else { stats.num_redundant++; - else - ASSERT(false); + + /** + * @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 */ @@ -508,7 +518,7 @@ void ModelChecker::print_execution(bool printbugs) const { print_program_output(); - if (DBG_ENABLED() || params.verbose) { + if (params.verbose) { model_print("Earliest divergence point since last feasible execution:\n"); if (earliest_diverge) earliest_diverge->print(); @@ -547,12 +557,13 @@ bool ModelChecker::next_execution() assert_bug("Deadlock detected"); checkDataRaces(); + run_trace_analyses(); } record_stats(); /* Output */ - if (DBG_ENABLED() || params.verbose || (complete && have_bug_reports())) + if (params.verbose || (complete && have_bug_reports())) print_execution(complete); else clear_program_output(); @@ -572,6 +583,12 @@ bool ModelChecker::next_execution() return true; } +/** @brief Run trace analyses on complete trace */ +void ModelChecker::run_trace_analyses() { + for (unsigned int i = 0; i < trace_analyses->size(); i++) + (*trace_analyses)[i]->analyze(action_trace); +} + /** * @brief Find the last fence-related backtracking conflict for a ModelAction * @@ -611,8 +628,8 @@ ModelAction * ModelChecker::get_last_fence_conflict(ModelAction *act) const * load-acquire * or * load --sb-> fence-acquire */ - std::vector< ModelAction *, ModelAlloc > acquire_fences(get_num_threads(), NULL); - std::vector< ModelAction *, ModelAlloc > prior_loads(get_num_threads(), NULL); + ModelVector acquire_fences(get_num_threads(), NULL); + ModelVector prior_loads(get_num_threads(), NULL); bool found_acquire_fences = false; for ( ; rit != list->rend(); rit++) { ModelAction *prev = *rit; @@ -754,6 +771,7 @@ void ModelChecker::set_backtracking(ModelAction *act) Node *node = prev->get_node()->get_parent(); + /* See Dynamic Partial Order Reduction (addendum), POPL '05 */ int low_tid, high_tid; if (node->enabled_status(t->get_id()) == THREAD_ENABLED) { low_tid = id_to_int(act->get_tid()); @@ -770,6 +788,7 @@ void ModelChecker::set_backtracking(ModelAction *act) if (i >= node->get_num_threads()) break; + /* See Dynamic Partial Order Reduction (addendum), POPL '05 */ /* Don't backtrack into a point where the thread is disabled or sleeping. */ if (node->enabled_status(tid) != THREAD_ENABLED) continue; @@ -791,6 +810,21 @@ void ModelChecker::set_backtracking(ModelAction *act) if (unfair) continue; } + + /* See if CHESS-like yield fairness allows */ + if (model->params.yieldon) { + 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_over(tid, tother)) { + unfair = true; + break; + } + } + if (unfair) + continue; + } + /* Cache the latest backtracking point */ set_latest_backtrack(prev); @@ -847,38 +881,39 @@ ModelAction * ModelChecker::get_next_backtrack() bool ModelChecker::process_read(ModelAction *curr) { Node *node = curr->get_node(); - uint64_t value = VALUE_NONE; - bool updated = false; while (true) { + bool updated = false; switch (node->get_read_from_status()) { case READ_FROM_PAST: { const ModelAction *rf = node->get_read_from_past(); ASSERT(rf); mo_graph->startChanges(); - value = rf->get_value(); - check_recency(curr, rf); - bool r_status = r_modification_order(curr, rf); - if (is_infeasible() && node->increment_read_from()) { - 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; + } } + updated = r_modification_order(curr, rf); read_from(curr, rf); mo_graph->commitChanges(); mo_check_promises(curr, true); - - updated |= r_status; break; } case READ_FROM_PROMISE: { Promise *promise = curr->get_node()->get_read_from_promise(); - promise->add_reader(curr); - value = promise->get_value(); + 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; @@ -887,7 +922,6 @@ bool ModelChecker::process_read(ModelAction *curr) /* Read from 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(); @@ -898,7 +932,7 @@ bool ModelChecker::process_read(ModelAction *curr) default: ASSERT(false); } - get_thread(curr)->set_return_value(value); + get_thread(curr)->set_return_value(curr->get_return_value()); return updated; } } @@ -921,20 +955,15 @@ bool ModelChecker::process_read(ModelAction *curr) */ 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(); + if (mutex) state = mutex->get_state(); - } else if (curr->is_wait()) { - mutex = (std::mutex *)curr->get_value(); - 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); @@ -946,41 +975,35 @@ 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) { - curr->synchronize_with(unlock); + synchronize(unlock, curr); return true; } break; } + case ATOMIC_WAIT: case ATOMIC_UNLOCK: { - //unlock the lock - state->islocked = false; - //wake up the other threads - action_list_t *waiters = get_safe_ptr_action(lock_waiters_map, curr->get_location()); - //activate all the waiting threads - for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) { - scheduler->wake(get_thread(*rit)); - } - waiters->clear(); - break; - } - case ATOMIC_WAIT: { - //unlock the lock - state->islocked = false; - //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 - for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) { - scheduler->wake(get_thread(*rit)); + /* wake up the other threads */ + for (unsigned int i = 0; i < get_num_threads(); i++) { + Thread *t = get_thread(int_to_id(i)); + Thread *curr_thrd = get_thread(curr); + if (t->waiting_on() == curr_thrd && t->get_pending()->is_lock()) + scheduler->wake(t); } - waiters->clear(); - //check whether we should go to sleep or not...simulate spurious failures + + /* unlock the lock - after checking who was waiting on it */ + state->locked = NULL; + + if (!curr->is_wait()) + break; /* The rest is only for ATOMIC_WAIT */ + + /* Should we go to sleep? (simulate spurious failures) */ if (curr->get_node()->get_misc() == 0) { get_safe_ptr_action(condvar_waiters_map, curr->get_location())->push_back(curr); - //disable us + /* disable us */ scheduler->sleep(get_thread(curr)); } break; @@ -1010,25 +1033,66 @@ bool ModelChecker::process_mutex(ModelAction *curr) return false; } +/** + * @brief Check if the current pending promises allow a future value to be sent + * + * If one of the following is true: + * (a) there are no pending promises + * (b) the reader and writer do not cross any promises + * Then, it is safe to pass a future value back now. + * + * Otherwise, we must save the pending future value until (a) or (b) is true + * + * @param writer The operation which sends the future value. Must be a write. + * @param reader The operation which will observe the value. Must be a read. + * @return True if the future value can be sent now; false if it must wait. + */ +bool ModelChecker::promises_may_allow(const ModelAction *writer, + const ModelAction *reader) const +{ + if (promises->empty()) + return true; + for(int i=promises->size()-1;i>=0;i--) { + ModelAction *pr=(*promises)[i]->get_reader(0); + //reader is after promise...doesn't cross any promise + if (*reader > *pr) + return true; + //writer is after promise, reader before...bad... + if (*writer > *pr) + return false; + } + return true; +} + +/** + * @brief Add a future value to a reader + * + * This function performs a few additional checks to ensure that the future + * value can be feasibly observed by the reader + * + * @param writer The operation whose value is sent. Must be a write. + * @param reader The read operation which may read the future value. Must be a read. + */ void ModelChecker::add_future_value(const ModelAction *writer, ModelAction *reader) { /* Do more ambitious checks now that mo is more complete */ - if (mo_may_allow(writer, reader)) { - Node *node = reader->get_node(); - - /* Find an ancestor thread which exists at the time of the reader */ - Thread *write_thread = get_thread(writer); - while (id_to_int(write_thread->get_id()) >= node->get_num_threads()) - write_thread = write_thread->get_parent(); - - struct future_value fv = { - writer->get_write_value(), - writer->get_seq_number() + params.maxfuturedelay, - write_thread->get_id(), - }; - if (node->add_future_value(fv)) - set_latest_backtrack(reader); - } + if (!mo_may_allow(writer, reader)) + return; + + Node *node = reader->get_node(); + + /* Find an ancestor thread which exists at the time of the reader */ + Thread *write_thread = get_thread(writer); + while (id_to_int(write_thread->get_id()) >= node->get_num_threads()) + write_thread = write_thread->get_parent(); + + struct future_value fv = { + writer->get_write_value(), + writer->get_seq_number() + params.maxfuturedelay, + write_thread->get_id(), + }; + if (node->add_future_value(fv)) + set_latest_backtrack(reader); } /** @@ -1038,15 +1102,42 @@ 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 */ + ModelVector send_fv; + + const ModelAction *earliest_promise_reader; + bool updated_promises = false; + + bool updated_mod_order = w_modification_order(curr, &send_fv); + Promise *promise = pop_promise_to_resolve(curr); + + if (promise) { + earliest_promise_reader = promise->get_reader(0); + updated_promises = resolve_promise(curr, promise); + } else + earliest_promise_reader = NULL; + + for (unsigned int i = 0; i < send_fv.size(); i++) { + ModelAction *read = send_fv[i]; - if (promises->size() == 0) { - for (unsigned int i = 0; i < futurevalues->size(); i++) { - struct PendingFutureValue pfv = (*futurevalues)[i]; - add_future_value(pfv.writer, pfv.act); + /* Don't send future values to reads after the Promise we resolve */ + if (!earliest_promise_reader || *read < *earliest_promise_reader) { + /* Check if future value can be sent immediately */ + if (promises_may_allow(curr, read)) { + add_future_value(curr, read); + } else { + futurevalues->push_back(PendingFutureValue(curr, read)); + } + } + } + + /* Check the pending future values */ + for (int i = (int)futurevalues->size() - 1; i >= 0; i--) { + struct PendingFutureValue pfv = (*futurevalues)[i]; + if (promises_may_allow(pfv.writer, pfv.reader)) { + add_future_value(pfv.writer, pfv.reader); + futurevalues->erase(futurevalues->begin() + i); } - futurevalues->clear(); } mo_graph->commitChanges(); @@ -1069,6 +1160,7 @@ bool ModelChecker::process_fence(ModelAction *curr) * use in later synchronization * fence-acquire (this function): search for hypothetical release * sequences + * fence-seq-cst: MO constraints formed in {r,w}_modification_order */ bool updated = false; if (curr->is_acquire()) { @@ -1097,8 +1189,7 @@ bool ModelChecker::process_fence(ModelAction *curr) rel_heads_list_t release_heads; get_release_seq_heads(curr, act, &release_heads); for (unsigned int i = 0; i < release_heads.size(); i++) - if (!curr->synchronize_with(release_heads[i])) - set_bad_synchronization(); + synchronize(release_heads[i], curr); if (release_heads.size() != 0) updated = true; } @@ -1139,15 +1230,18 @@ bool ModelChecker::process_thread_action(ModelAction *curr) case THREAD_JOIN: { Thread *blocking = curr->get_thread_operand(); ModelAction *act = get_last_action(blocking->get_id()); - curr->synchronize_with(act); + synchronize(act, curr); updated = true; /* trigger rel-seq checks */ break; } case THREAD_FINISH: { Thread *th = get_thread(curr); - while (!th->wait_list_empty()) { - ModelAction *act = th->pop_wait_list(); - scheduler->wake(get_thread(act)); + /* Wake up any joining threads */ + for (unsigned int i = 0; i < get_num_threads(); i++) { + Thread *waiting = get_thread(int_to_id(i)); + if (waiting->waiting_on() == th && + waiting->get_pending()->is_thread_join()) + scheduler->wake(waiting); } th->complete(); /* Completed thread can't satisfy promises */ @@ -1210,10 +1304,8 @@ void ModelChecker::process_relseq_fixup(ModelAction *curr, work_queue_t *work_qu */ /* Must synchronize */ - if (!acquire->synchronize_with(release)) { - set_bad_synchronization(); + if (!synchronize(release, acquire)) return; - } /* Re-check all pending release sequences */ work_queue->push_back(CheckRelSeqWorkEntry(NULL)); /* Re-check act for mo_graph edges */ @@ -1224,7 +1316,7 @@ void ModelChecker::process_relseq_fixup(ModelAction *curr, work_queue_t *work_qu for (; (*rit) != acquire; rit++) { ModelAction *propagate = *rit; if (acquire->happens_before(propagate)) { - propagate->synchronize_with(acquire); + synchronize(acquire, propagate); /* Re-check 'propagate' for mo_graph edges */ work_queue->push_back(MOEdgeWorkEntry(propagate)); } @@ -1325,21 +1417,43 @@ bool ModelChecker::initialize_curr_action(ModelAction **curr) bool ModelChecker::read_from(ModelAction *act, const ModelAction *rf) { ASSERT(rf); + ASSERT(rf->is_write()); + act->set_read_from(rf); if (act->is_acquire()) { rel_heads_list_t release_heads; get_release_seq_heads(act, act, &release_heads); int num_heads = release_heads.size(); for (unsigned int i = 0; i < release_heads.size(); i++) - if (!act->synchronize_with(release_heads[i])) { - set_bad_synchronization(); + if (!synchronize(release_heads[i], act)) num_heads--; - } return num_heads > 0; } return false; } +/** + * @brief Synchronizes two actions + * + * When A synchronizes with B (or A --sw-> B), B inherits A's clock vector. + * This function performs the synchronization as well as providing other hooks + * for other checks along with synchronization. + * + * @param first The left-hand side of the synchronizes-with relation + * @param second The right-hand side of the synchronizes-with relation + * @return True if the synchronization was successful (i.e., was consistent + * with the execution order); false otherwise + */ +bool ModelChecker::synchronize(const ModelAction *first, ModelAction *second) +{ + if (*second < *first) { + set_bad_synchronization(); + return false; + } + check_promises(first->get_tid(), second->get_cv(), first->get_cv()); + return second->synchronize_with(first); +} + /** * 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 @@ -1381,17 +1495,13 @@ void ModelChecker::thread_blocking_check_promises(Thread *blocker, Thread *waiti */ bool ModelChecker::check_action_enabled(ModelAction *curr) { if (curr->is_lock()) { - std::mutex *lock = (std::mutex *)curr->get_location(); + std::mutex *lock = curr->get_mutex(); struct std::mutex_state *state = lock->get_state(); - if (state->islocked) { - //Stick the action in the appropriate waiting queue - get_safe_ptr_action(lock_waiters_map, curr->get_location())->push_back(curr); + if (state->locked) return false; - } - } else if (curr->get_type() == THREAD_JOIN) { - Thread *blocking = (Thread *)curr->get_location(); + } else if (curr->is_thread_join()) { + Thread *blocking = curr->get_thread_operand(); if (!blocking->is_complete()) { - blocking->push_wait_list(curr); thread_blocking_check_promises(blocking, get_thread(curr)); return false; } @@ -1415,23 +1525,17 @@ ModelAction * ModelChecker::check_current_action(ModelAction *curr) { ASSERT(curr); bool second_part_of_rmw = curr->is_rmwc() || curr->is_rmw(); - - if (!check_action_enabled(curr)) { - /* Make the execution look like we chose to run this action - * much later, when a lock/join can succeed */ - get_thread(curr)->set_pending(curr); - scheduler->sleep(get_thread(curr)); - return NULL; - } - bool newly_explored = initialize_curr_action(&curr); DBG(); - if (DBG_ENABLED()) - curr->print(); wake_up_sleeping_actions(curr); + /* Compute fairness information for CHESS yield algorithm */ + if (model->params.yieldon) { + curr->get_node()->update_yield(scheduler); + } + /* Add the action to lists before any other model-checking tasks */ if (!second_part_of_rmw) add_action_to_lists(curr); @@ -1496,7 +1600,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(); @@ -1617,105 +1721,118 @@ 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. + * 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. * - * 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. - * - * 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_past_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_past_size() <= 1) - return; - } - for (int i = 0; i < curr->get_node()->get_read_from_past_size(); i++) { - /* Get write */ - const ModelAction *write = curr->get_node()->get_read_from_past(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 **/ + SnapVector *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_past_size(); j++) { - if (act->get_node()->get_read_from_past(j) == write) { - foundvalue = true; - break; - } - } - if (!foundvalue) { - feasiblewrite = false; - break; - } - } - if (feasiblewrite) { - priv->too_many_reads = true; - return; - } - } + SnapVector *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; } /** - * Updates the mo_graph with the constraints imposed from the current + * @brief Updates the mo_graph with the constraints imposed from the current * read. * * Basic idea is the following: Go through each other thread and find * the last action that happened before our read. Two cases: * - * (1) The action is a write => that write must either occur before + * -# The action is a write: that write must either occur before * the write we read from or be the write we read from. - * - * (2) The action is a read => the write that that action read from + * -# The action is a read: the write that that action read from * must occur before the write we read from or be the same write. * * @param curr The current action. Must be a read. @@ -1725,13 +1842,16 @@ void ModelChecker::check_recency(ModelAction *curr, const ModelAction *rf) template bool ModelChecker::r_modification_order(ModelAction *curr, const rf_type *rf) { - std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); + SnapVector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); unsigned int i; bool added = false; ASSERT(curr->is_read()); /* Last SC fence in the current thread */ ModelAction *last_sc_fence_local = get_last_seq_cst_fence(curr->get_tid(), NULL); + ModelAction *last_sc_write = NULL; + if (curr->is_seqcst()) + last_sc_write = get_last_seq_cst_write(curr); /* Iterate over all threads */ for (i = 0; i < thrd_lists->size(); i++) { @@ -1751,7 +1871,18 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const rf_type *rf) for (rit = list->rbegin(); rit != list->rend(); rit++) { ModelAction *act = *rit; - if (act->is_write() && !act->equals(rf) && act != curr) { + /* Skip curr */ + if (act == curr) + continue; + /* Don't want to add reflexive edges on 'rf' */ + if (act->equals(rf)) { + if (act->happens_before(curr)) + break; + else + continue; + } + + if (act->is_write()) { /* C++, Section 29.3 statement 5 */ if (curr->is_seqcst() && last_sc_fence_thread_local && *act < *last_sc_fence_thread_local) { @@ -1772,15 +1903,19 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const rf_type *rf) } } + /* C++, Section 29.3 statement 3 (second subpoint) */ + if (curr->is_seqcst() && last_sc_write && act == last_sc_write) { + added = mo_graph->addEdge(act, rf) || added; + break; + } + /* * Include at most one act per-thread that "happens - * before" curr. Don't consider reflexively. + * before" curr */ - if (act->happens_before(curr) && act != curr) { + if (act->happens_before(curr)) { if (act->is_write()) { - if (!act->equals(rf)) { - added = mo_graph->addEdge(act, rf) || added; - } + added = mo_graph->addEdge(act, rf) || added; } else { const ModelAction *prevrf = act->get_reads_from(); const Promise *prevrf_promise = act->get_reads_from_promise(); @@ -1827,11 +1962,13 @@ 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, ModelVector *send_fv) { - std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); + SnapVector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); unsigned int i; bool added = false; ASSERT(curr->is_write()); @@ -1922,9 +2059,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); } @@ -1947,7 +2084,7 @@ 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) +bool ModelChecker::thin_air_constraint_may_allow(const ModelAction *writer, const ModelAction *reader) const { if (!writer->is_rmw()) return true; @@ -1975,7 +2112,7 @@ bool ModelChecker::thin_air_constraint_may_allow(const ModelAction *writer, cons */ bool ModelChecker::mo_may_allow(const ModelAction *writer, const ModelAction *reader) { - std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, reader->get_location()); + SnapVector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, reader->get_location()); unsigned int i; /* Iterate over all threads */ for (i = 0; i < thrd_lists->size(); i++) { @@ -2076,7 +2213,7 @@ bool ModelChecker::release_seq_heads(const ModelAction *rf, release_heads->push_back(fence_release); int tid = id_to_int(rf->get_tid()); - std::vector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, rf->get_location()); + SnapVector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, rf->get_location()); action_list_t *list = &(*thrd_lists)[tid]; action_list_t::const_reverse_iterator rit; @@ -2219,7 +2356,7 @@ void ModelChecker::get_release_seq_heads(ModelAction *acquire, bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_queue) { bool updated = false; - std::vector< struct release_seq *, SnapshotAlloc >::iterator it = pending_rel_seqs->begin(); + SnapVector::iterator it = pending_rel_seqs->begin(); while (it != pending_rel_seqs->end()) { struct release_seq *pending = *it; ModelAction *acquire = pending->acquire; @@ -2235,14 +2372,10 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_ rel_heads_list_t release_heads; bool complete; complete = release_seq_heads(rf, &release_heads, pending); - for (unsigned int i = 0; i < release_heads.size(); i++) { - if (!acquire->has_synchronized_with(release_heads[i])) { - if (acquire->synchronize_with(release_heads[i])) + for (unsigned int i = 0; i < release_heads.size(); i++) + if (!acquire->has_synchronized_with(release_heads[i])) + if (synchronize(release_heads[i], acquire)) updated = true; - else - set_bad_synchronization(); - } - } if (updated) { /* Re-check all pending release sequences */ @@ -2256,7 +2389,7 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_ for (; (*rit) != acquire; rit++) { ModelAction *propagate = *rit; if (acquire->happens_before(propagate)) { - propagate->synchronize_with(acquire); + synchronize(acquire, propagate); /* Re-check 'propagate' for mo_graph edges */ work_queue->push_back(MOEdgeWorkEntry(propagate)); } @@ -2290,9 +2423,9 @@ void ModelChecker::add_action_to_lists(ModelAction *act) int uninit_id = -1; action_list_t *list = get_safe_ptr_action(obj_map, act->get_location()); if (list->empty() && act->is_atomic_var()) { - uninit = new_uninitialized_action(act->get_location()); + uninit = get_uninitialized_action(act); uninit_id = id_to_int(uninit->get_tid()); - list->push_back(uninit); + list->push_front(uninit); } list->push_back(act); @@ -2300,7 +2433,7 @@ void ModelChecker::add_action_to_lists(ModelAction *act) if (uninit) action_trace->push_front(uninit); - std::vector *vec = get_safe_ptr_vect_action(obj_thrd_map, act->get_location()); + SnapVector *vec = get_safe_ptr_vect_action(obj_thrd_map, act->get_location()); if (tid >= (int)vec->size()) vec->resize(priv->next_thread_id); (*vec)[tid].push_back(act); @@ -2323,7 +2456,7 @@ void ModelChecker::add_action_to_lists(ModelAction *act) void *mutex_loc = (void *) act->get_value(); get_safe_ptr_action(obj_map, mutex_loc)->push_back(act); - std::vector *vec = get_safe_ptr_vect_action(obj_thrd_map, mutex_loc); + SnapVector *vec = get_safe_ptr_vect_action(obj_thrd_map, mutex_loc); if (tid >= (int)vec->size()) vec->resize(priv->next_thread_id); (*vec)[tid].push_back(act); @@ -2372,8 +2505,11 @@ ModelAction * ModelChecker::get_last_seq_cst_write(ModelAction *curr) const action_list_t *list = get_safe_ptr_action(obj_map, location); /* Find: max({i in dom(S) | seq_cst(t_i) && isWrite(t_i) && samevar(t_i, t)}) */ action_list_t::reverse_iterator rit; - for (rit = list->rbegin(); rit != list->rend(); rit++) - if ((*rit)->is_write() && (*rit)->is_seqcst() && (*rit) != curr) + for (rit = list->rbegin(); (*rit) != curr; rit++) + ; + rit++; /* Skip past curr */ + for ( ; rit != list->rend(); rit++) + if ((*rit)->is_write() && (*rit)->is_seqcst()) return *rit; return NULL; } @@ -2446,53 +2582,61 @@ 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. - * @param write The ModelAction that is fulfilling Promises - * @return True if promises were resolved; false otherwise + * @brief Find the promise (if any) to resolve for the current action and + * remove it from the pending promise vector + * @param curr The current ModelAction. Should be a write. + * @return The Promise to resolve, if any; otherwise NULL */ -bool ModelChecker::resolve_promises(ModelAction *write) +Promise * ModelChecker::pop_promise_to_resolve(const ModelAction *curr) { - 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)) { - for (unsigned int j = 0; j < promise->get_num_readers(); j++) { - ModelAction *read = promise->get_reader(j); - 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)); - mo_graph->resolvePromise(promise, write, &mustResolve); + for (unsigned int i = 0; i < promises->size(); i++) + if (curr->get_node()->get_promise(i)) { + Promise *ret = (*promises)[i]; + promises->erase(promises->begin() + i); + return ret; + } + return NULL; +} - resolved.push_back(promise); - promises->erase(promises->begin() + promise_index); +/** + * Resolve a Promise with a current write. + * @param write The ModelAction that is fulfilling Promises + * @param promise The Promise to resolve + * @return True if the Promise was successfully resolved; false otherwise + */ +bool ModelChecker::resolve_promise(ModelAction *write, Promise *promise) +{ + ModelVector actions_to_check; - 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; + + /** + * @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; } /** @@ -2635,7 +2779,7 @@ void ModelChecker::compute_relseq_breakwrites(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()); + SnapVector *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location()); unsigned int i; ASSERT(curr->is_read()); @@ -2726,14 +2870,21 @@ bool ModelChecker::sleep_can_read_from(ModelAction *curr, const ModelAction *wri } /** - * @brief Create a new action representing an uninitialized atomic - * @param location The memory location of the atomic object - * @return A pointer to a new ModelAction + * @brief Get an action representing an uninitialized atomic + * + * This function may create a new one or try to retrieve one from the NodeStack + * + * @param curr The current action, which prompts the creation of an UNINIT action + * @return A pointer to the UNINIT ModelAction */ -ModelAction * ModelChecker::new_uninitialized_action(void *location) const +ModelAction * ModelChecker::get_uninitialized_action(const ModelAction *curr) const { - ModelAction *act = (ModelAction *)snapshot_malloc(sizeof(class ModelAction)); - act = new (act) ModelAction(ATOMIC_UNINIT, std::memory_order_relaxed, location, 0, model_thread); + Node *node = curr->get_node(); + ModelAction *act = node->get_uninit_action(); + if (!act) { + act = new ModelAction(ATOMIC_UNINIT, std::memory_order_relaxed, curr->get_location(), model->params.uninitvalue, model_thread); + node->set_uninit_action(act); + } act->create_cv(NULL); return act; } @@ -2747,7 +2898,9 @@ static void print_list(action_list_t *list) unsigned int hash = 0; for (it = list->begin(); it != list->end(); it++) { - (*it)->print(); + const ModelAction *act = *it; + if (act->get_seq_number() > 0) + act->print(); hash = hash^(hash<<3)^((*it)->hash()); } model_print("HASH %u\n", hash); @@ -2814,6 +2967,14 @@ void ModelChecker::print_summary() const print_infeasibility(" INFEASIBLE"); print_list(action_trace); model_print("\n"); + if (!promises->empty()) { + model_print("Pending promises:\n"); + for (unsigned int i = 0; i < promises->size(); i++) { + model_print(" [P%u] ", i); + (*promises)[i]->print(); + } + model_print("\n"); + } } /** @@ -2827,15 +2988,6 @@ void ModelChecker::add_thread(Thread *t) scheduler->add_thread(t); } -/** - * Removes a thread from the scheduler. - * @param the thread to remove. - */ -void ModelChecker::remove_thread(Thread *t) -{ - scheduler->remove_thread(t); -} - /** * @brief Get a Thread reference by its ID * @param tid The Thread's ID @@ -2924,6 +3076,7 @@ uint64_t ModelChecker::switch_to_master(ModelAction *act) { DBG(); Thread *old = thread_current(); + scheduler->set_current_thread(NULL); ASSERT(!old->get_pending()); old->set_pending(act); if (Thread::swap(old, &system_context) < 0) { @@ -2944,28 +3097,14 @@ Thread * ModelChecker::take_step(ModelAction *curr) Thread *curr_thrd = get_thread(curr); ASSERT(curr_thrd->get_state() == THREAD_READY); + ASSERT(check_action_enabled(curr)); /* May have side effects? */ curr = check_current_action(curr); - - /* Infeasible -> don't take any more steps */ - if (is_infeasible()) - return NULL; - else if (isfeasibleprefix() && have_bug_reports()) { - set_assert(); - return NULL; - } - - if (params.bound != 0 && priv->used_sequence_numbers > params.bound) - return NULL; + ASSERT(curr); if (curr_thrd->is_blocked() || curr_thrd->is_complete()) scheduler->remove_thread(curr_thrd); - Thread *next_thrd = get_next_thread(curr); - - DEBUG("(%d, %d)\n", curr_thrd ? id_to_int(curr_thrd->get_id()) : -1, - next_thrd ? id_to_int(next_thrd->get_id()) : -1); - - return next_thrd; + return action_select_next_thread(curr); } /** Wrapper to run the user's main function, with appropriate arguments */ @@ -2974,6 +3113,27 @@ void user_main_wrapper(void *) user_main(model->params.argc, model->params.argv); } +/** @return True if the execution has taken too many steps */ +bool ModelChecker::too_many_steps() const +{ + return params.bound != 0 && priv->used_sequence_numbers > params.bound; +} + +bool ModelChecker::should_terminate_execution() +{ + /* Infeasible -> don't take any more steps */ + if (is_infeasible()) + return true; + else if (isfeasibleprefix() && have_bug_reports()) { + set_assert(); + return true; + } + + if (too_many_steps()) + return true; + return false; +} + /** @brief Run ModelChecker for the user program */ void ModelChecker::run() { @@ -2994,6 +3154,17 @@ void ModelChecker::run() Thread *thr = get_thread(tid); if (!thr->is_model_thread() && !thr->is_complete() && !thr->get_pending()) { switch_from_master(thr); + if (thr->is_waiting_on(thr)) + assert_bug("Deadlock detected (thread %u)", i); + } + } + + /* Don't schedule threads which should be disabled */ + for (unsigned int i = 0; i < get_num_threads(); i++) { + Thread *th = get_thread(int_to_id(i)); + ModelAction *act = th->get_pending(); + if (act && is_enabled(th) && !check_action_enabled(act)) { + scheduler->sleep(th); } } @@ -3002,11 +3173,16 @@ void ModelChecker::run() if (has_asserted()) break; + if (!t) + t = get_next_thread(); + if (!t || t->is_model_thread()) + break; + /* Consume the next action for a Thread */ ModelAction *curr = t->get_pending(); t->set_pending(NULL); t = take_step(curr); - } while (t && !t->is_model_thread()); + } while (!should_terminate_execution()); /* * Launch end-of-execution release sequence fixups only when