#include <stdio.h>
+#include <algorithm>
#include "model.h"
#include "action.h"
#include "clockvector.h"
#include "cyclegraph.h"
#include "promise.h"
+#include "datarace.h"
#define INITIAL_THREAD_ID 0
ModelChecker::ModelChecker(struct model_params params) :
/* Initialize default scheduler */
scheduler(new Scheduler()),
- /* First thread created will have id INITIAL_THREAD_ID */
- next_thread_id(INITIAL_THREAD_ID),
- used_sequence_numbers(0),
num_executions(0),
params(params),
- current_action(NULL),
diverge(NULL),
- nextThread(THREAD_ID_T_NONE),
action_trace(new action_list_t()),
thread_map(new HashTable<int, Thread *, int>()),
obj_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
lazy_sync_with_release(new HashTable<void *, std::list<ModelAction *>, uintptr_t, 4>()),
thrd_last_action(new std::vector<ModelAction *>(1)),
node_stack(new NodeStack()),
- next_backtrack(NULL),
mo_graph(new CycleGraph()),
- failed_promise(false)
+ failed_promise(false),
+ too_many_reads(false),
+ asserted(false)
{
+ /* Allocate this "size" on the snapshotting heap */
+ priv = (struct model_snapshot_members *)calloc(1, sizeof(*priv));
+ /* First thread created will have id INITIAL_THREAD_ID */
+ priv->next_thread_id = INITIAL_THREAD_ID;
+
+ lazy_sync_size = &priv->lazy_sync_size;
}
/** @brief Destructor */
{
DEBUG("+++ Resetting to initial state +++\n");
node_stack->reset_execution();
- current_action = NULL;
- next_thread_id = INITIAL_THREAD_ID;
- used_sequence_numbers = 0;
- nextThread = 0;
- next_backtrack = NULL;
failed_promise = false;
+ too_many_reads = false;
+ reset_asserted();
snapshotObject->backTrackBeforeStep(0);
}
/** @returns a thread ID for a new Thread */
thread_id_t ModelChecker::get_next_id()
{
- return next_thread_id++;
+ return priv->next_thread_id++;
}
/** @returns the number of user threads created during this execution */
int ModelChecker::get_num_threads()
{
- return next_thread_id;
+ return priv->next_thread_id;
}
/** @returns a sequence number for a new ModelAction */
modelclock_t ModelChecker::get_next_seq_num()
{
- return ++used_sequence_numbers;
-}
-
-/**
- * Performs the "scheduling" for the model-checker. That is, it checks if the
- * model-checker has selected a "next thread to run" and returns it, if
- * available. This function should be called from the Scheduler routine, where
- * the Scheduler falls back to a default scheduling routine if needed.
- *
- * @return The next thread chosen by the model-checker. If the model-checker
- * makes no selection, retuns NULL.
- */
-Thread * ModelChecker::schedule_next_thread()
-{
- Thread *t;
- if (nextThread == THREAD_ID_T_NONE)
- return NULL;
- t = thread_map->get(id_to_int(nextThread));
-
- ASSERT(t != NULL);
-
- return t;
+ return ++priv->used_sequence_numbers;
}
/**
- * Choose the next thread in the replay sequence.
+ * @brief Choose the next thread to execute.
*
- * If the replay sequence has reached the 'diverge' point, returns a thread
- * from the backtracking set. Otherwise, simply returns the next thread in the
- * sequence that is being replayed.
+ * 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 this will return
+ * NULL.
+ * @param curr The current ModelAction. This action might guide the choice of
+ * next thread.
+ * @return The next thread to run. If the model-checker has no preference, NULL.
*/
-thread_id_t ModelChecker::get_next_replay_thread()
+Thread * ModelChecker::get_next_thread(ModelAction *curr)
{
thread_id_t tid;
+ /* Do not split atomic actions. */
+ if (curr->is_rmwr())
+ return thread_current();
+ /* The THREAD_CREATE action points to the created Thread */
+ else if (curr->get_type() == THREAD_CREATE)
+ return (Thread *)curr->get_location();
+
/* Have we completed exploring the preselected path? */
if (diverge == NULL)
- return THREAD_ID_T_NONE;
+ return NULL;
/* Else, we are trying to replay an execution */
ModelAction *next = node_stack->get_next()->get_action();
tid = next->get_tid();
}
DEBUG("*** ModelChecker chose next thread = %d ***\n", tid);
- return tid;
+ ASSERT(tid != THREAD_ID_T_NONE);
+ return thread_map->get(id_to_int(tid));
}
/**
if (isfinalfeasible() || DBG_ENABLED())
print_summary();
- if ((diverge = model->get_next_backtrack()) == NULL)
+ if ((diverge = get_next_backtrack()) == NULL)
return false;
if (DBG_ENABLED()) {
diverge->print();
}
- model->reset_to_initial_state();
+ reset_to_initial_state();
return true;
}
{
ModelAction *prev;
Node *node;
- Thread *t = get_thread(act->get_tid());
+ Thread *t = get_thread(act);
prev = get_last_conflict(act);
if (prev == NULL)
return;
/* Cache the latest backtracking point */
- if (!next_backtrack || *prev > *next_backtrack)
- next_backtrack = prev;
+ if (!priv->next_backtrack || *prev > *priv->next_backtrack)
+ priv->next_backtrack = prev;
/* If this is a new backtracking point, mark the tree */
if (!node->set_backtrack(t->get_id()))
*/
ModelAction * ModelChecker::get_next_backtrack()
{
- ModelAction *next = next_backtrack;
- next_backtrack = NULL;
+ ModelAction *next = priv->next_backtrack;
+ priv->next_backtrack = NULL;
return next;
}
-void ModelChecker::check_current_action(void)
-{
- ModelAction *curr = this->current_action;
- bool already_added = false;
- this->current_action = NULL;
- if (!curr) {
- DEBUG("trying to push NULL action...\n");
- return;
+/**
+ * Processes a read or rmw model action.
+ * @param curr is the read model action to process.
+ * @param th is the thread
+ * @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, Thread * th, bool second_part_of_rmw) {
+ uint64_t value;
+ bool updated=false;
+ while(true) {
+ const ModelAction *reads_from = curr->get_node()->get_read_from();
+ if (reads_from != NULL) {
+ mo_graph->startChanges();
+
+ value = reads_from->get_value();
+ /* Assign reads_from, perform release/acquire synchronization */
+ curr->read_from(reads_from);
+ if (!second_part_of_rmw) {
+ check_recency(curr,false);
+ }
+
+ bool r_status=r_modification_order(curr,reads_from);
+
+ if (!second_part_of_rmw&&!isfeasible()&&(curr->get_node()->increment_read_from()||!curr->get_node()->future_value_empty())) {
+ mo_graph->rollbackChanges();
+ too_many_reads=false;
+ continue;
+ }
+
+ mo_graph->commitChanges();
+ updated |= r_status;
+ } else {
+ /* Read from future value */
+ value = curr->get_node()->get_future_value();
+ modelclock_t expiration = curr->get_node()->get_future_value_expiration();
+ curr->read_from(NULL);
+ Promise *valuepromise = new Promise(curr, value, expiration);
+ promises->push_back(valuepromise);
+ }
+ th->set_return_value(value);
+ return updated;
}
+}
+
+/**
+ * This is the heart of the model checker routine. It performs model-checking
+ * actions corresponding to a given "current action." Among other processes, it
+ * calculates reads-from relationships, updates synchronization clock vectors,
+ * forms a memory_order constraints graph, and handles replay/backtrack
+ * execution when running permutations of previously-observed executions.
+ *
+ * @param curr The current action to process
+ * @return The next Thread that must be executed. May be NULL if ModelChecker
+ * makes no choice (e.g., according to replay execution, combining RMW actions,
+ * etc.)
+ */
+Thread * ModelChecker::check_current_action(ModelAction *curr)
+{
+ bool second_part_of_rmw = false;
+
+ ASSERT(curr);
if (curr->is_rmwc() || curr->is_rmw()) {
ModelAction *tmp = process_rmw(curr);
- already_added = true;
+ second_part_of_rmw = true;
delete curr;
curr = tmp;
+ compute_promises(curr);
} else {
ModelAction *tmp = node_stack->explore_action(curr);
if (tmp) {
}
}
- /* Assign 'creation' parent */
- if (curr->get_type() == THREAD_CREATE) {
+ /* Thread specific actions */
+ switch(curr->get_type()) {
+ case THREAD_CREATE: {
Thread *th = (Thread *)curr->get_location();
th->set_creation(curr);
+ break;
}
-
- /* Deal with new thread */
- if (curr->get_type() == THREAD_START)
+ case THREAD_JOIN: {
+ Thread *waiting, *blocking;
+ waiting = get_thread(curr);
+ blocking = (Thread *)curr->get_location();
+ if (!blocking->is_complete()) {
+ blocking->push_wait_list(curr);
+ scheduler->sleep(waiting);
+ }
+ break;
+ }
+ case THREAD_FINISH: {
+ Thread *th = get_thread(curr);
+ while (!th->wait_list_empty()) {
+ ModelAction *act = th->pop_wait_list();
+ Thread *wake = get_thread(act);
+ scheduler->wake(wake);
+ }
+ th->complete();
+ break;
+ }
+ case THREAD_START: {
check_promises(NULL, curr->get_cv());
+ break;
+ }
+ default:
+ break;
+ }
+
+ Thread *th = get_thread(curr);
- /* Assign reads_from values */
- Thread *th = get_thread(curr->get_tid());
- uint64_t value = VALUE_NONE;
bool updated = false;
if (curr->is_read()) {
- const ModelAction *reads_from = curr->get_node()->get_read_from();
- if (reads_from != NULL) {
- value = reads_from->get_value();
- /* Assign reads_from, perform release/acquire synchronization */
- curr->read_from(reads_from);
- if (r_modification_order(curr,reads_from))
- updated = true;
- } else {
- /* Read from future value */
- value = curr->get_node()->get_future_value();
- curr->read_from(NULL);
- Promise *valuepromise = new Promise(curr, value);
- promises->push_back(valuepromise);
- }
- } else if (curr->is_write()) {
- if (w_modification_order(curr))
- updated = true;;
- if (resolve_promises(curr))
- updated = true;
+ updated=process_read(curr, th, second_part_of_rmw);
+ }
+
+ if (curr->is_write()) {
+ bool updated_mod_order=w_modification_order(curr);
+ bool updated_promises=resolve_promises(curr);
+ updated=updated_mod_order|updated_promises;
+
+ mo_graph->commitChanges();
+ th->set_return_value(VALUE_NONE);
}
if (updated)
resolve_release_sequences(curr->get_location());
- th->set_return_value(value);
-
/* Add action to list. */
- if (!already_added)
+ if (!second_part_of_rmw)
add_action_to_lists(curr);
- /** @todo Is there a better interface for setting the next thread rather
- than this field/convoluted approach? Perhaps like just returning
- it or something? */
+ check_curr_backtracking(curr);
+
+ set_backtracking(curr);
- /* Do not split atomic actions. */
- if (curr->is_rmwr())
- nextThread = thread_current()->get_id();
- else
- nextThread = get_next_replay_thread();
+ return get_next_thread(curr);
+}
+void ModelChecker::check_curr_backtracking(ModelAction * curr) {
Node *currnode = curr->get_node();
Node *parnode = currnode->get_parent();
+
+ if ((!parnode->backtrack_empty() ||
+ !currnode->read_from_empty() ||
+ !currnode->future_value_empty() ||
+ !currnode->promise_empty())
+ && (!priv->next_backtrack ||
+ *curr > *priv->next_backtrack)) {
+ priv->next_backtrack = curr;
+ }
+}
- if (!parnode->backtrack_empty() || !currnode->read_from_empty() ||
- !currnode->future_value_empty() || !currnode->promise_empty())
- if (!next_backtrack || *curr > *next_backtrack)
- next_backtrack = curr;
- set_backtracking(curr);
+bool ModelChecker::promises_expired() {
+ for (unsigned int promise_index = 0; promise_index < promises->size(); promise_index++) {
+ Promise *promise = (*promises)[promise_index];
+ if (promise->get_expiration()<priv->used_sequence_numbers) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/** @returns whether the current partial trace must be a prefix of a
+ * feasible trace. */
+bool ModelChecker::isfeasibleprefix() {
+ return promises->size() == 0 && *lazy_sync_size == 0;
}
/** @returns whether the current partial trace is feasible. */
bool ModelChecker::isfeasible() {
- return !mo_graph->checkForCycles() && !failed_promise;
+ return !mo_graph->checkForCycles() && !failed_promise && !too_many_reads && !promises_expired();
}
/** Returns whether the current completed trace is feasible. */
int tid = id_to_int(act->get_tid());
ModelAction *lastread = get_last_action(tid);
lastread->process_rmw(act);
- if (act->is_rmw())
+ if (act->is_rmw() && lastread->get_reads_from()!=NULL) {
mo_graph->addRMWEdge(lastread->get_reads_from(), lastread);
+ mo_graph->commitChanges();
+ }
return lastread;
}
+/**
+ * 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.
+ *
+ * If so, we decide that the execution is no longer feasible.
+ */
+void ModelChecker::check_recency(ModelAction *curr, bool already_added) {
+ 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 (!isfeasible())
+ return;
+
+ std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(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 */
+ if (already_added) {
+ 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() != curr->get_reads_from())
+ 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
+ if (write==curr->get_reads_from())
+ continue;
+
+ /* Test to see whether this is a feasible write to read from*/
+ mo_graph->startChanges();
+ r_modification_order(curr, write);
+ bool feasiblereadfrom=isfeasible();
+ mo_graph->rollbackChanges();
+
+ if (!feasiblereadfrom)
+ continue;
+ rit=ritcopy;
+
+ bool feasiblewrite=true;
+ //new we need to see if this write works for everyone
+
+ 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(i)==write) {
+ foundvalue=true;
+ break;
+ }
+ }
+ if (!foundvalue) {
+ feasiblewrite=false;
+ break;
+ }
+ }
+ if (feasiblewrite) {
+ too_many_reads = true;
+ return;
+ }
+ }
+ }
+}
+
/**
* Updates the mo_graph with the constraints imposed from the current read.
* @param curr The current action. Must be a read.
/* Include at most one act per-thread that "happens before" curr */
if (act->happens_before(curr)) {
- if (act->is_read())
+ /*
+ * Note: if act is RMW, just add edge:
+ * act --mo--> curr
+ * The following edge should be handled elsewhere:
+ * readfrom(act) --mo--> act
+ */
+ if (act->is_write()) {
+ //RMW shouldn't have an edge to themselves
+ if (act!=curr)
+ mo_graph->addEdge(act, curr);
+ } else if (act->is_read() && act->get_reads_from() != NULL)
mo_graph->addEdge(act->get_reads_from(), curr);
- else
- mo_graph->addEdge(act, curr);
added = true;
break;
} else if (act->is_read() && !act->is_synchronizing(curr) &&
=>
that read could potentially read from our write.
*/
- if (act->get_node()->add_future_value(curr->get_value()) &&
- (!next_backtrack || *act > *next_backtrack))
- next_backtrack = act;
+ if (isfeasible() && act->get_node()->add_future_value(curr->get_value(), curr->get_seq_number()+params.maxfuturedelay) &&
+ (!priv->next_backtrack || *act > *priv->next_backtrack))
+ priv->next_backtrack = act;
}
}
}
if (rf->is_release())
release_heads->push_back(rf);
if (rf->is_rmw()) {
- if (rf->is_acquire())
+ /* We need a RMW action that is both an acquire and release to stop */
+ /** @todo Need to be smarter here... In the linux lock
+ * example, this will run to the beginning of the program for
+ * every acquire. */
+ if (rf->is_acquire() && rf->is_release())
return true; /* complete */
return release_seq_head(rf->get_reads_from(), release_heads);
}
action_list_t::const_reverse_iterator rit;
/* Find rf in the thread list */
- for (rit = list->rbegin(); rit != list->rend(); rit++)
- if (*rit == rf)
- break;
+ rit = std::find(list->rbegin(), list->rend(), rf);
+ ASSERT(rit != list->rend());
/* Find the last write/release */
for (; rit != list->rend(); rit++)
if (id_to_int(rf->get_tid()) == (int)i)
continue;
list = &(*thrd_lists)[i];
+
+ /* Can we ensure no future writes from this thread may break
+ * the release seq? */
+ bool future_ordered = false;
+
for (rit = list->rbegin(); rit != list->rend(); rit++) {
const ModelAction *act = *rit;
if (!act->is_write())
/* Reach synchronization -> this thread is complete */
if (act->happens_before(release))
break;
- if (rf->happens_before(act))
+ if (rf->happens_before(act)) {
+ future_ordered = true;
continue;
+ }
/* Check modification order */
- if (mo_graph->checkReachable(rf, act))
+ if (mo_graph->checkReachable(rf, act)) {
/* rf --mo--> act */
+ future_ordered = true;
continue;
+ }
if (mo_graph->checkReachable(act, release))
/* act --mo--> release */
break;
}
certain = false;
}
+ if (!future_ordered)
+ return false; /* This thread is uncertain */
}
if (certain)
std::list<ModelAction *> *list;
list = lazy_sync_with_release->get_safe_ptr(act->get_location());
list->push_back(act);
+ (*lazy_sync_size)++;
}
}
propagate->synchronize_with(act);
}
}
- if (complete)
+ if (complete) {
it = list->erase(it);
- else
+ (*lazy_sync_size)--;
+ } else
it++;
}
+ // If we resolved promises or data races, see if we have realized a data race.
+ if (checkDataRaces()) {
+ set_assert();
+ }
+
return updated;
}
std::vector<action_list_t> *vec = obj_thrd_map->get_safe_ptr(act->get_location());
if (tid >= (int)vec->size())
- vec->resize(next_thread_id);
+ vec->resize(priv->next_thread_id);
(*vec)[tid].push_back(act);
if ((int)thrd_last_action->size() <= tid)
if (write->get_node()->get_promise(i)) {
ModelAction *read = promise->get_action();
read->read_from(write);
+ if (read->is_rmw()) {
+ mo_graph->addRMWEdge(write, read);
+ }
r_modification_order(read, write);
post_r_modification_order(read, write);
promises->erase(promises->begin() + promise_index);
} else
promise_index++;
}
+
return resolved;
}
merge_cv->synchronized_since(act)) {
//This thread is no longer able to send values back to satisfy the promise
int num_synchronized_threads = promise->increment_threads();
- if (num_synchronized_threads == model->get_num_threads()) {
+ if (num_synchronized_threads == get_num_threads()) {
//Promise has failed
failed_promise = true;
return;
continue;
/* Don't consider more than one seq_cst write if we are a seq_cst read. */
- if (!act->is_seqcst() || !curr->is_seqcst() || act == last_seq_cst) {
+ if (!curr->is_seqcst()|| (!act->is_seqcst() && (last_seq_cst==NULL||!act->happens_before(last_seq_cst))) || act == last_seq_cst) {
DEBUG("Adding action to may_read_from:\n");
if (DBG_ENABLED()) {
act->print();
* context). This switch is made with the intention of exploring a particular
* model-checking action (described by a ModelAction object). Must be called
* from a user-thread context.
- * @param act The current action that will be explored. May be NULL, although
- * there is little reason to switch to the model-checker without an action to
- * explore (note: act == NULL is sometimes used as a hack to allow a thread to
- * yield control without performing any progress; see thrd_join()).
+ * @param act The current action that will be explored. Must not be NULL.
* @return Return status from the 'swap' call (i.e., success/fail, 0/-1)
*/
int ModelChecker::switch_to_master(ModelAction *act)
bool ModelChecker::take_step() {
Thread *curr, *next;
+ if (has_asserted())
+ return false;
+
curr = thread_current();
if (curr) {
if (curr->get_state() == THREAD_READY) {
- check_current_action();
- scheduler->add_thread(curr);
- } else if (curr->get_state() == THREAD_RUNNING) {
- /* Stopped while running; i.e., completed */
- curr->complete();
+ ASSERT(priv->current_action);
+ priv->nextThread = check_current_action(priv->current_action);
+ priv->current_action = NULL;
+ if (!curr->is_blocked() && !curr->is_complete())
+ scheduler->add_thread(curr);
} else {
ASSERT(false);
}
}
- next = scheduler->next_thread();
+ next = scheduler->next_thread(priv->nextThread);
/* Infeasible -> don't take any more steps */
if (!isfeasible())