X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=model.cc;h=72610f6581e500231941a1613274891556687f3f;hb=8f82e4c697b8f4ca7b3d4e79ace1b9cf1dc259d2;hp=417252c3f92ceb3a4bb7948612ca3592f1be49de;hpb=1798c510e77f37e3d79cba6a1fdad3b13ccc9674;p=model-checker.git diff --git a/model.cc b/model.cc index 417252c..72610f6 100644 --- a/model.cc +++ b/model.cc @@ -13,6 +13,7 @@ #include "promise.h" #include "datarace.h" #include "threads-model.h" +#include "output.h" #define INITIAL_THREAD_ID 0 @@ -27,19 +28,50 @@ struct bug_message { ~bug_message() { if (msg) snapshot_free(msg); } char *msg; - void print() { printf("%s", msg); } + void print() { model_print("%s", msg); } + + SNAPSHOTALLOC }; /** * Structure for holding small ModelChecker members that should be snapshotted */ struct model_snapshot_members { + model_snapshot_members() : + current_action(NULL), + /* First thread created will have id INITIAL_THREAD_ID */ + next_thread_id(INITIAL_THREAD_ID), + used_sequence_numbers(0), + nextThread(NULL), + next_backtrack(NULL), + bugs(), + stats(), + failed_promise(false), + too_many_reads(false), + bad_synchronization(false), + asserted(false) + { } + + ~model_snapshot_members() { + for (unsigned int i = 0; i < bugs.size(); i++) + delete bugs[i]; + bugs.clear(); + } + ModelAction *current_action; unsigned int next_thread_id; modelclock_t used_sequence_numbers; Thread *nextThread; ModelAction *next_backtrack; std::vector< bug_message *, SnapshotAlloc > bugs; + struct execution_stats stats; + bool failed_promise; + bool too_many_reads; + /** @brief Incorrectly-ordered synchronization was made */ + bool bad_synchronization; + bool asserted; + + SNAPSHOTALLOC }; /** @brief Constructor */ @@ -47,8 +79,6 @@ ModelChecker::ModelChecker(struct model_params params) : /* Initialize default scheduler */ params(params), scheduler(new Scheduler()), - num_executions(0), - num_feasible_executions(0), diverge(NULL), earliest_diverge(NULL), action_trace(new action_list_t()), @@ -62,17 +92,9 @@ ModelChecker::ModelChecker(struct model_params params) : pending_rel_seqs(new std::vector< struct release_seq *, SnapshotAlloc >()), thrd_last_action(new std::vector< ModelAction *, SnapshotAlloc >(1)), node_stack(new NodeStack()), - mo_graph(new CycleGraph()), - failed_promise(false), - too_many_reads(false), - asserted(false), - bad_synchronization(false) + priv(new struct model_snapshot_members()), + mo_graph(new CycleGraph()) { - /* Allocate this "size" on the snapshotting heap */ - priv = (struct model_snapshot_members *)snapshot_calloc(1, sizeof(*priv)); - /* First thread created will have id INITIAL_THREAD_ID */ - priv->next_thread_id = INITIAL_THREAD_ID; - /* Initialize a model-checker thread, for special ModelActions */ model_thread = new Thread(get_next_id()); thread_map->put(id_to_int(model_thread->get_id()), model_thread); @@ -101,11 +123,7 @@ ModelChecker::~ModelChecker() delete node_stack; delete scheduler; delete mo_graph; - - for (unsigned int i = 0; i < priv->bugs.size(); i++) - delete priv->bugs[i]; - priv->bugs.clear(); - snapshot_free(priv); + delete priv; } static action_list_t * get_safe_ptr_action(HashTable * hash, void * ptr) { @@ -134,10 +152,10 @@ void ModelChecker::reset_to_initial_state() { DEBUG("+++ Resetting to initial state +++\n"); node_stack->reset_execution(); - failed_promise = false; - too_many_reads = false; - bad_synchronization = false; - reset_asserted(); + + /* Print all model-checker output before rollback */ + fflush(model_out); + snapshotObject->backTrackBeforeStep(0); } @@ -291,6 +309,23 @@ void ModelChecker::wake_up_sleeping_actions(ModelAction * curr) { } } +/** @brief Alert the model-checker that an incorrectly-ordered + * synchronization was made */ +void ModelChecker::set_bad_synchronization() +{ + priv->bad_synchronization = true; +} + +bool ModelChecker::has_asserted() const +{ + return priv->asserted; +} + +void ModelChecker::set_assert() +{ + priv->asserted = true; +} + /** * Check if we are in a deadlock. Should only be called at the end of an * execution, although it should not give false positives in the middle of an @@ -336,45 +371,30 @@ bool ModelChecker::is_complete_execution() const * the current trace is not yet feasible, the error message will be stashed and * printed if the execution ever becomes feasible. * - * This function can also be used to immediately trigger the bug; that is, we - * don't wait for a feasible execution before aborting. Only use the - * "immediate" option when you know that the infeasibility is justified (e.g., - * pending release sequences are not a problem) - * * @param msg Descriptive message for the bug (do not include newline char) - * @param user_thread Was this assertion triggered from a user thread? - * @param immediate Should this bug be triggered immediately? + * @return True if bug is immediately-feasible */ -void ModelChecker::assert_bug(const char *msg, bool user_thread, bool immediate) +bool ModelChecker::assert_bug(const char *msg) { priv->bugs.push_back(new bug_message(msg)); - if (immediate || isfeasibleprefix()) { + if (isfeasibleprefix()) { set_assert(); - if (user_thread) - switch_to_master(NULL); + return true; } + return false; } /** - * @brief Assert a bug in the executing program, with a default message - * @see ModelChecker::assert_bug - * @param user_thread Was this assertion triggered from a user thread? - */ -void ModelChecker::assert_bug(bool user_thread) -{ - assert_bug("bug detected", user_thread); -} - -/** - * @brief Assert a bug in the executing program immediately + * @brief Assert a bug in the executing program, asserted by a user thread * @see ModelChecker::assert_bug * @param msg Descriptive message for the bug (do not include newline char) */ -void ModelChecker::assert_bug_immediate(const char *msg) +void ModelChecker::assert_user_bug(const char *msg) { - printf("Feasible: %s\n", isfeasibleprefix() ? "yes" : "no"); - assert_bug(msg, false, true); + /* If feasible bug, bail out now */ + if (assert_bug(msg)) + switch_to_master(NULL); } /** @return True, if any bugs have been reported for this execution */ @@ -387,50 +407,109 @@ bool ModelChecker::have_bug_reports() const void ModelChecker::print_bugs() const { if (have_bug_reports()) { - printf("Bug report: %zu bugs detected\n", priv->bugs.size()); + model_print("Bug report: %zu bug%s detected\n", + priv->bugs.size(), + priv->bugs.size() > 1 ? "s" : ""); for (unsigned int i = 0; i < priv->bugs.size(); i++) priv->bugs[i]->print(); } } /** - * Queries the model-checker for more executions to explore and, if one - * exists, resets the model-checker state to execute a new execution. + * @brief Record end-of-execution stats * - * @return If there are more executions to explore, return true. Otherwise, - * return false. + * Must be run when exiting an execution. Records various stats. + * @see struct execution_stats */ -bool ModelChecker::next_execution() +void ModelChecker::record_stats() { - DBG(); + stats.num_total++; + if (!isfinalfeasible()) + stats.num_infeasible++; + else if (have_bug_reports()) + stats.num_buggy_executions++; + else if (is_complete_execution()) + stats.num_complete++; + else + stats.num_redundant++; +} + +/** @brief Print execution stats */ +void ModelChecker::print_stats() const +{ + model_print("Number of complete, bug-free executions: %d\n", stats.num_complete); + model_print("Number of redundant executions: %d\n", stats.num_redundant); + model_print("Number of buggy executions: %d\n", stats.num_buggy_executions); + model_print("Number of infeasible executions: %d\n", stats.num_infeasible); + model_print("Total executions: %d\n", stats.num_total); + model_print("Total nodes created: %d\n", node_stack->get_total_nodes()); +} - num_executions++; +/** + * @brief End-of-exeuction print + * @param printbugs Should any existing bugs be printed? + */ +void ModelChecker::print_execution(bool printbugs) const +{ + print_program_output(); - if (isfinalfeasible() && (is_complete_execution() || have_bug_reports())) { - printf("Earliest divergence point since last feasible execution:\n"); + if (DBG_ENABLED() || params.verbose) { + model_print("Earliest divergence point since last feasible execution:\n"); if (earliest_diverge) earliest_diverge->print(); else - printf("(Not set)\n"); + model_print("(Not set)\n"); - earliest_diverge = NULL; - num_feasible_executions++; + model_print("\n"); + print_stats(); + } + /* Don't print invalid bugs */ + if (printbugs) + print_bugs(); + + model_print("\n"); + print_summary(); +} + +/** + * Queries the model-checker for more executions to explore and, if one + * exists, resets the model-checker state to execute a new execution. + * + * @return If there are more executions to explore, return true. Otherwise, + * return false. + */ +bool ModelChecker::next_execution() +{ + DBG(); + /* Is this execution a feasible execution that's worth bug-checking? */ + bool complete = isfinalfeasible() && (is_complete_execution() || + have_bug_reports()); + + /* End-of-execution bug checks */ + if (complete) { if (is_deadlocked()) assert_bug("Deadlock detected"); - print_bugs(); checkDataRaces(); - print_summary(); - } else if (DBG_ENABLED()) { - print_summary(); } + record_stats(); + + /* Output */ + if (DBG_ENABLED() || params.verbose || have_bug_reports()) + print_execution(complete); + else + clear_program_output(); + + if (complete) + earliest_diverge = NULL; + if ((diverge = get_next_backtrack()) == NULL) return false; if (DBG_ENABLED()) { - printf("Next execution will diverge at:\n"); + model_print("Next execution will diverge at:\n"); diverge->print(); } @@ -444,6 +523,9 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act) case ATOMIC_READ: case ATOMIC_WRITE: case ATOMIC_RMW: { + /* Optimization: relaxed operations don't need backtracking */ + if (act->is_relaxed()) + return NULL; /* linear search: from most recent to oldest */ action_list_t *list = get_safe_ptr_action(obj_map, act->get_location()); action_list_t::reverse_iterator rit; @@ -615,7 +697,7 @@ bool ModelChecker::process_read(ModelAction *curr, bool second_part_of_rmw) if (!second_part_of_rmw&&!isfeasible()&&(curr->get_node()->increment_read_from()||curr->get_node()->increment_future_value())) { mo_graph->rollbackChanges(); - too_many_reads = false; + priv->too_many_reads = false; continue; } @@ -887,8 +969,7 @@ void ModelChecker::process_relseq_fixup(ModelAction *curr, work_queue_t *work_qu } /* See if we have realized a data race */ - if (checkDataRaces()) - assert_bug("Datarace"); + checkDataRaces(); } /** @@ -1037,7 +1118,7 @@ Thread * ModelChecker::check_current_action(ModelAction *curr) /* Initialize work_queue with the "current action" work */ work_queue_t work_queue(1, CheckCurrWorkEntry(curr)); - while (!work_queue.empty()) { + while (!work_queue.empty() && !has_asserted()) { WorkQueueEntry work = work_queue.front(); work_queue.pop_front(); @@ -1152,16 +1233,16 @@ bool ModelChecker::isfeasibleotherthanRMW() const if (DBG_ENABLED()) { if (mo_graph->checkForCycles()) DEBUG("Infeasible: modification order cycles\n"); - if (failed_promise) + if (priv->failed_promise) DEBUG("Infeasible: failed promise\n"); - if (too_many_reads) + if (priv->too_many_reads) DEBUG("Infeasible: too many reads\n"); - if (bad_synchronization) + if (priv->bad_synchronization) DEBUG("Infeasible: bad synchronization ordering\n"); if (promises_expired()) DEBUG("Infeasible: promises expired\n"); } - return !mo_graph->checkForCycles() && !failed_promise && !too_many_reads && !bad_synchronization && !promises_expired(); + return !mo_graph->checkForCycles() && !priv->failed_promise && !priv->too_many_reads && !priv->bad_synchronization && !promises_expired(); } /** Returns whether the current completed trace is feasible. */ @@ -1270,7 +1351,7 @@ void ModelChecker::check_recency(ModelAction *curr, const ModelAction *rf) { } } if (feasiblewrite) { - too_many_reads = true; + priv->too_many_reads = true; return; } } @@ -1819,8 +1900,7 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_ } // If we resolved promises or data races, see if we have realized a data race. - if (checkDataRaces()) - assert_bug("Datarace"); + checkDataRaces(); return updated; } @@ -2013,7 +2093,7 @@ void ModelChecker::check_promises(thread_id_t tid, ClockVector *old_cv, ClockVec merge_cv->synchronized_since(act)) { if (promise->increment_threads(tid)) { //Promise has failed - failed_promise = true; + priv->failed_promise = true; return; } } @@ -2024,7 +2104,7 @@ void ModelChecker::check_promises_thread_disabled() { for (unsigned int i = 0; i < promises->size(); i++) { Promise *promise = (*promises)[i]; if (promise->check_promise()) { - failed_promise = true; + priv->failed_promise = true; return; } } @@ -2075,12 +2155,12 @@ void ModelChecker::mo_check_promises(thread_id_t tid, const ModelAction *write) promise->set_write(write); //The pwrite cannot happen before the promise if (write->happens_before(act) && (write != act)) { - failed_promise = true; + priv->failed_promise = true; return; } } if (mo_graph->checkPromise(write, promise)) { - failed_promise = true; + priv->failed_promise = true; return; } } @@ -2091,7 +2171,7 @@ void ModelChecker::mo_check_promises(thread_id_t tid, const ModelAction *write) if (promise->get_write()&&mo_graph->checkReachable(promise->get_write(), write)) { if (promise->increment_threads(tid)) { - failed_promise = true; + priv->failed_promise = true; return; } } @@ -2179,18 +2259,15 @@ void ModelChecker::build_reads_from_past(ModelAction *curr) } } - if (!initialized) { - /** @todo Need a more informative way of reporting errors. */ - printf("ERROR: may read from uninitialized atomic\n"); - set_assert(); - } + if (!initialized) + assert_bug("May read from uninitialized atomic"); if (DBG_ENABLED() || !initialized) { - printf("Reached read action:\n"); + model_print("Reached read action:\n"); curr->print(); - printf("Printing may_read_from\n"); + model_print("Printing may_read_from\n"); curr->get_node()->print_may_read_from(); - printf("End printing may_read_from\n"); + model_print("End printing may_read_from\n"); } } @@ -2210,20 +2287,22 @@ bool ModelChecker::sleep_can_read_from(ModelAction * curr, const ModelAction *wr } } -static void print_list(action_list_t *list) +static void print_list(action_list_t *list, int exec_num = -1) { action_list_t::iterator it; - printf("---------------------------------------------------------------------\n"); - printf("Trace:\n"); + model_print("---------------------------------------------------------------------\n"); + if (exec_num >= 0) + model_print("Execution %d:\n", exec_num); + unsigned int hash=0; for (it = list->begin(); it != list->end(); it++) { (*it)->print(); hash=hash^(hash<<3)^((*it)->hash()); } - printf("HASH %u\n", hash); - printf("---------------------------------------------------------------------\n"); + model_print("HASH %u\n", hash); + model_print("---------------------------------------------------------------------\n"); } #if SUPPORT_MOD_ORDER_DUMP @@ -2254,26 +2333,22 @@ void ModelChecker::dumpGraph(char *filename) { } #endif -void ModelChecker::print_summary() +/** @brief Prints an execution trace summary. */ +void ModelChecker::print_summary() const { - printf("\n"); - printf("Number of executions: %d\n", num_executions); - printf("Number of feasible executions: %d\n", num_feasible_executions); - printf("Total nodes created: %d\n", node_stack->get_total_nodes()); - #if SUPPORT_MOD_ORDER_DUMP scheduler->print(); char buffername[100]; - sprintf(buffername, "exec%04u", num_executions); + sprintf(buffername, "exec%04u", stats.num_total); mo_graph->dumpGraphToFile(buffername); - sprintf(buffername, "graph%04u", num_executions); + sprintf(buffername, "graph%04u", stats.num_total); dumpGraph(buffername); #endif if (!isfinalfeasible()) - printf("INFEASIBLE EXECUTION!\n"); - print_list(action_trace); - printf("\n"); + model_print("INFEASIBLE EXECUTION!\n"); + print_list(action_trace, stats.num_total); + model_print("\n"); } /** @@ -2408,7 +2483,7 @@ bool ModelChecker::take_step() { */ if (!pending_rel_seqs->empty() && (!next || next->is_model_thread()) && isfinalfeasible() && !unrealizedraces.empty()) { - printf("*** WARNING: release sequence fixup action (%zu pending release seuqences) ***\n", + model_print("*** WARNING: release sequence fixup action (%zu pending release seuqences) ***\n", pending_rel_seqs->size()); ModelAction *fixup = new ModelAction(MODEL_FIXUP_RELSEQ, std::memory_order_seq_cst, NULL, VALUE_NONE, @@ -2435,9 +2510,24 @@ bool ModelChecker::take_step() { return (Thread::swap(&system_context, next) == 0); } -/** Runs the current execution until threre are no more steps to take. */ -void ModelChecker::finish_execution() { - DBG(); +/** Wrapper to run the user's main function, with appropriate arguments */ +void user_main_wrapper(void *) +{ + user_main(model->params.argc, model->params.argv); +} + +/** @brief Run ModelChecker for the user program */ +void ModelChecker::run() +{ + do { + thrd_t user_thread; + + /* Start user program */ + add_thread(new Thread(&user_thread, &user_main_wrapper, NULL)); + + /* Wait for all threads to complete */ + while (take_step()); + } while (next_execution()); - while (take_step()); + print_stats(); }