X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;ds=sidebyside;f=model.cc;h=a7558c8c3a67dba4636264dd038d0204d37009c7;hb=c108b7d5709061ced258f5106ac67ada91242fc3;hp=7ce959e90b9c5aeaf649df1f422868a63aa34421;hpb=9452f3d15b8e205aaa74dab361b9727a1b78d0a0;p=cdsspec-compiler.git diff --git a/model.cc b/model.cc index 7ce959e..a7558c8 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 */ @@ -83,7 +72,6 @@ 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 SnapVector()), @@ -92,6 +80,7 @@ ModelChecker::ModelChecker(struct model_params params) : 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; @@ -147,6 +138,18 @@ static SnapVector * get_safe_ptr_vect_action(HashTable *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 @@ -260,20 +260,8 @@ Thread * ModelChecker::get_next_thread() 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 { @@ -300,7 +288,7 @@ Thread * ModelChecker::get_next_thread() } 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)); } /** @@ -409,22 +397,6 @@ 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 @@ -452,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(); @@ -578,6 +557,7 @@ bool ModelChecker::next_execution() assert_bug("Deadlock detected"); checkDataRaces(); + run_trace_analyses(); } record_stats(); @@ -603,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 * @@ -785,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()); @@ -801,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; @@ -996,32 +984,26 @@ bool ModelChecker::process_mutex(ModelAction *curr) } break; } + case ATOMIC_WAIT: case ATOMIC_UNLOCK: { - //unlock the lock - 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 - 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(); - break; - } - case ATOMIC_WAIT: { - //unlock the lock + + /* unlock the lock - after checking who was waiting on it */ 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 - for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) { - scheduler->wake(get_thread(*rit)); - } - waiters->clear(); - //check whether we should go to sleep or not...simulate spurious failures + + 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; @@ -1051,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); } /** @@ -1094,19 +1117,27 @@ bool ModelChecker::process_write(ModelAction *curr) } 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)); + + /* 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)); + } + } } - if (promises->empty()) { - for (unsigned int i = 0; i < futurevalues->size(); i++) { - struct PendingFutureValue pfv = (*futurevalues)[i]; + /* 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(); @@ -1206,9 +1237,12 @@ bool ModelChecker::process_thread_action(ModelAction *curr) } 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 */ @@ -1444,17 +1478,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->locked) { - //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; } @@ -1478,20 +1508,9 @@ 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); @@ -1788,16 +1807,15 @@ bool ModelChecker::check_recency(ModelAction *curr, const T *rf) const } /** - * 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. @@ -3066,32 +3084,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 = NULL; - if (curr) - next_thrd = action_select_next_thread(curr); - if (!next_thrd) - next_thrd = get_next_thread(); - - 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 */ @@ -3100,6 +3100,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() { @@ -3120,8 +3141,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 (is_circular_wait(thr)) - assert_bug("Deadlock detected"); + 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); } } @@ -3130,11 +3160,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