#include "promise.h"
#include "datarace.h"
#include "mutex.h"
-#include "threads.h"
+#include "threads-model.h"
#define INITIAL_THREAD_ID 0
obj_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
lock_waiters_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
obj_thrd_map(new HashTable<void *, std::vector<action_list_t>, uintptr_t, 4 >()),
- promises(new std::vector<Promise *>()),
- futurevalues(new std::vector<struct PendingFutureValue>()),
- pending_rel_seqs(new std::vector<struct release_seq *>()),
- thrd_last_action(new std::vector<ModelAction *>(1)),
+ promises(new std::vector< Promise *, SnapshotAlloc<Promise *> >()),
+ futurevalues(new std::vector< struct PendingFutureValue, SnapshotAlloc<struct PendingFutureValue> >()),
+ pending_rel_seqs(new std::vector< struct release_seq *, SnapshotAlloc<struct release_seq *> >()),
+ thrd_last_action(new std::vector< ModelAction *, SnapshotAlloc<ModelAction *> >(1)),
node_stack(new NodeStack()),
mo_graph(new CycleGraph()),
failed_promise(false),
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;
+
+ /* 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);
}
/** @brief Destructor */
earliest_diverge=diverge;
Node *nextnode = next->get_node();
+ Node *prevnode = nextnode->get_parent();
+ scheduler->update_sleep_set(prevnode);
+
/* Reached divergence point */
if (nextnode->increment_promise()) {
/* The next node will try to satisfy a different set of promises. */
/* The next node will try to read from a different future value. */
tid = next->get_tid();
node_stack->pop_restofstack(2);
+ } else if (nextnode->increment_relseq_break()) {
+ /* The next node will try to resolve a release sequence differently */
+ tid = next->get_tid();
+ node_stack->pop_restofstack(2);
} else {
/* Make a different thread execute for next step */
- Node *node = nextnode->get_parent();
- tid = node->get_next_backtrack();
+ scheduler->add_sleep(thread_map->get(id_to_int(next->get_tid())));
+ tid = prevnode->get_next_backtrack();
+ /* Make sure the backtracked thread isn't sleeping. */
node_stack->pop_restofstack(1);
if (diverge==earliest_diverge) {
- earliest_diverge=node->get_action();
+ earliest_diverge=prevnode->get_action();
}
}
+ /* The correct sleep set is in the parent node. */
+ execute_sleep_set();
+
DEBUG("*** Divergence point ***\n");
diverge = NULL;
return thread_map->get(id_to_int(tid));
}
+/**
+ * We need to know what the next actions of all threads in the sleep
+ * set will be. This method computes them and stores the actions at
+ * the corresponding thread object's pending action.
+ */
+
+void ModelChecker::execute_sleep_set() {
+ for(unsigned int i=0;i<get_num_threads();i++) {
+ thread_id_t tid=int_to_id(i);
+ Thread *thr=get_thread(tid);
+ if ( scheduler->get_enabled(thr) == THREAD_SLEEP_SET ) {
+ thr->set_state(THREAD_RUNNING);
+ scheduler->next_thread(thr);
+ Thread::swap(&system_context, thr);
+ priv->current_action->set_sleep_flag();
+ thr->set_pending(priv->current_action);
+ }
+ }
+ priv->current_action = NULL;
+}
+
+void ModelChecker::wake_up_sleeping_actions(ModelAction * curr) {
+ for(unsigned int i=0;i<get_num_threads();i++) {
+ thread_id_t tid=int_to_id(i);
+ Thread *thr=get_thread(tid);
+ if ( scheduler->get_enabled(thr) == THREAD_SLEEP_SET ) {
+ ModelAction *pending_act=thr->get_pending();
+ if (pending_act->could_synchronize_with(curr)) {
+ //Remove this thread from sleep set
+ scheduler->remove_sleep(thr);
+ }
+ }
+ }
+}
+
/**
* Queries the model-checker for more executions to explore and, if one
* exists, resets the model-checker state to execute a new execution.
num_feasible_executions++;
}
- DEBUG("Number of acquires waiting on pending release sequences: %lu\n",
+ DEBUG("Number of acquires waiting on pending release sequences: %zu\n",
pending_rel_seqs->size());
- if (isfinalfeasible() || DBG_ENABLED())
+
+ if (isfinalfeasible() || (params.bound != 0 && priv->used_sequence_numbers > params.bound ) || DBG_ENABLED() )
print_summary();
if ((diverge = get_next_backtrack()) == NULL)
return NULL;
}
-/** This method find backtracking points where we should try to
+/** This method finds backtracking points where we should try to
* reorder the parameter ModelAction against.
*
* @param the ModelAction to find backtracking points for.
for(int i = low_tid; i < high_tid; i++) {
thread_id_t tid = int_to_id(i);
- if (!node->is_enabled(tid))
- continue;
+ /* Don't backtrack into a point where the thread is disabled or sleeping. */
+ if (node->get_enabled_array()[i]!=THREAD_ENABLED)
+ continue;
+
/* Check if this has been explored already */
if (node->has_been_explored(tid))
continue;
if (unfair)
continue;
}
-
/* Cache the latest backtracking point */
if (!priv->next_backtrack || *prev > *priv->next_backtrack)
priv->next_backtrack = prev;
if (promises->size() == 0) {
for (unsigned int i = 0; i < futurevalues->size(); i++) {
struct PendingFutureValue pfv = (*futurevalues)[i];
- if (pfv.act->get_node()->add_future_value(pfv.value, pfv.expiration) &&
+ //Do more ambitious checks now that mo is more complete
+ if (mo_may_allow(pfv.writer, pfv.act)&&
+ pfv.act->get_node()->add_future_value(pfv.writer->get_value(), pfv.writer->get_seq_number()+params.maxfuturedelay) &&
(!priv->next_backtrack || *pfv.act > *priv->next_backtrack))
priv->next_backtrack = pfv.act;
}
break;
}
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);
- } else {
- do_complete_join(curr);
- updated = true; /* trigger rel-seq checks */
- }
+ Thread *blocking = (Thread *)curr->get_location();
+ ModelAction *act = get_last_action(blocking->get_id());
+ curr->synchronize_with(act);
+ 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();
- Thread *wake = get_thread(act);
- scheduler->wake(wake);
- do_complete_join(act);
- updated = true; /* trigger rel-seq checks */
+ scheduler->wake(get_thread(act));
}
th->complete();
updated = true; /* trigger rel-seq checks */
return updated;
}
+/**
+ * @brief Process the current action for release sequence fixup activity
+ *
+ * Performs model-checker release sequence fixups for the current action,
+ * forcing a single pending release sequence to break (with a given, potential
+ * "loose" write) or to complete (i.e., synchronize). If a pending release
+ * sequence forms a complete release sequence, then we must perform the fixup
+ * synchronization, mo_graph additions, etc.
+ *
+ * @param curr The current action; must be a release sequence fixup action
+ * @param work_queue The work queue to which to add work items as they are
+ * generated
+ */
+void ModelChecker::process_relseq_fixup(ModelAction *curr, work_queue_t *work_queue)
+{
+ const ModelAction *write = curr->get_node()->get_relseq_break();
+ struct release_seq *sequence = pending_rel_seqs->back();
+ pending_rel_seqs->pop_back();
+ ASSERT(sequence);
+ ModelAction *acquire = sequence->acquire;
+ const ModelAction *rf = sequence->rf;
+ const ModelAction *release = sequence->release;
+ ASSERT(acquire);
+ ASSERT(release);
+ ASSERT(rf);
+ ASSERT(release->same_thread(rf));
+
+ if (write == NULL) {
+ /**
+ * @todo Forcing a synchronization requires that we set
+ * modification order constraints. For instance, we can't allow
+ * a fixup sequence in which two separate read-acquire
+ * operations read from the same sequence, where the first one
+ * synchronizes and the other doesn't. Essentially, we can't
+ * allow any writes to insert themselves between 'release' and
+ * 'rf'
+ */
+
+ /* Must synchronize */
+ if (!acquire->synchronize_with(release)) {
+ set_bad_synchronization();
+ return;
+ }
+ /* Re-check all pending release sequences */
+ work_queue->push_back(CheckRelSeqWorkEntry(NULL));
+ /* Re-check act for mo_graph edges */
+ work_queue->push_back(MOEdgeWorkEntry(acquire));
+
+ /* propagate synchronization to later actions */
+ action_list_t::reverse_iterator rit = action_trace->rbegin();
+ for (; (*rit) != acquire; rit++) {
+ ModelAction *propagate = *rit;
+ if (acquire->happens_before(propagate)) {
+ propagate->synchronize_with(acquire);
+ /* Re-check 'propagate' for mo_graph edges */
+ work_queue->push_back(MOEdgeWorkEntry(propagate));
+ }
+ }
+ } else {
+ /* Break release sequence with new edges:
+ * release --mo--> write --mo--> rf */
+ mo_graph->addEdge(release, write);
+ mo_graph->addEdge(write, rf);
+ }
+
+ /* See if we have realized a data race */
+ if (checkDataRaces())
+ set_assert();
+}
+
/**
* Initialize the current action by performing one or more of the following
* actions, as appropriate: merging RMWR and RMWC/RMW actions, stepping forward
*/
if (newcurr->is_write())
compute_promises(newcurr);
+ else if (newcurr->is_relseq_fixup())
+ compute_relseq_breakwrites(newcurr);
}
return newcurr;
}
/**
- * This method checks whether a model action is enabled at the given point.
- * At this point, it checks whether a lock operation would be successful at this point.
- * If not, it puts the thread in a waiter list.
+ * @brief Check whether a model action is enabled.
+ *
+ * Checks whether a lock or join operation would be successful (i.e., is the
+ * lock already locked, or is the joined thread already complete). If not, put
+ * the action in a waiter list.
+ *
* @param curr is the ModelAction to check whether it is enabled.
* @return a bool that indicates whether the action is enabled.
*/
lock_waiters_map->get_safe_ptr(curr->get_location())->push_back(curr);
return false;
}
+ } else if (curr->get_type() == THREAD_JOIN) {
+ Thread *blocking = (Thread *)curr->get_location();
+ if (!blocking->is_complete()) {
+ blocking->push_wait_list(curr);
+ return false;
+ }
}
return true;
Thread * 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 is actually available to release */
+ * much later, when a lock/join can succeed */
get_current_thread()->set_pending(curr);
scheduler->sleep(get_current_thread());
return get_next_thread(NULL);
ModelAction *newcurr = initialize_curr_action(curr);
+ wake_up_sleeping_actions(curr);
+
/* Add the action to lists before any other model-checking tasks */
if (!second_part_of_rmw)
add_action_to_lists(newcurr);
/* Initialize work_queue with the "current action" work */
work_queue_t work_queue(1, CheckCurrWorkEntry(curr));
-
while (!work_queue.empty()) {
WorkQueueEntry work = work_queue.front();
work_queue.pop_front();
if (act->is_mutex_op() && process_mutex(act))
update_all = true;
+ if (act->is_relseq_fixup())
+ process_relseq_fixup(curr, &work_queue);
+
if (update_all)
work_queue.push_back(CheckRelSeqWorkEntry(NULL));
else if (update)
}
check_curr_backtracking(curr);
-
set_backtracking(curr);
-
return get_next_thread(curr);
}
-/**
- * Complete a THREAD_JOIN operation, by synchronizing with the THREAD_FINISH
- * operation from the Thread it is joining with. Must be called after the
- * completion of the Thread in question.
- * @param join The THREAD_JOIN action
- */
-void ModelChecker::do_complete_join(ModelAction *join)
-{
- Thread *blocking = (Thread *)join->get_location();
- ModelAction *act = get_last_action(blocking->get_id());
- join->synchronize_with(act);
-}
-
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())
+ !currnode->promise_empty() ||
+ !currnode->relseq_break_empty())
&& (!priv->next_backtrack ||
*curr > *priv->next_backtrack)) {
priv->next_backtrack = curr;
(3) cannot synchronize with us
(4) is in a different thread
=>
- that read could potentially read from our write.
+ that read could potentially read from our write. Note that
+ these checks are overly conservative at this point, we'll
+ do more checks before actually removing the
+ pendingfuturevalue.
+
*/
if (thin_air_constraint_may_allow(curr, act)) {
if (isfeasible() ||
(curr->is_rmw() && act->is_rmw() && curr->get_reads_from() == act->get_reads_from() && isfeasibleotherthanRMW())) {
- struct PendingFutureValue pfv = {curr->get_value(),curr->get_seq_number()+params.maxfuturedelay,act};
+ struct PendingFutureValue pfv = {curr,act};
futurevalues->push_back(pfv);
}
}
return true;
}
+/** 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::mo_may_allow(const ModelAction * writer, const ModelAction *reader) {
+ std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(reader->get_location());
+
+ //Get write that follows reader action
+ action_list_t *list = &(*thrd_lists)[id_to_int(reader->get_tid())];
+ action_list_t::reverse_iterator rit;
+ ModelAction *first_write_after_read=NULL;
+
+ for (rit = list->rbegin(); rit != list->rend(); rit++) {
+ ModelAction *act = *rit;
+ if (act==reader)
+ break;
+ if (act->is_write())
+ first_write_after_read=act;
+ }
+
+ if (first_write_after_read==NULL)
+ return true;
+
+ return !mo_graph->checkReachable(first_write_after_read, writer);
+}
+
+
+
/**
* Finds the head(s) of the release sequence(s) containing a given ModelAction.
* The ModelAction under consideration is expected to be taking part in
* "returns" two pieces of data: a pass-by-reference vector of @a release_heads
* and a boolean representing certainty.
*
- * @todo Finish lazy updating, when promises are fulfilled in the future
* @param rf The action that might be part of a release sequence. Must be a
* write.
* @param release_heads A pass-by-reference style return parameter. After
bool future_ordered = false;
ModelAction *last = get_last_action(int_to_id(i));
- if (last && (rf->happens_before(last) ||
- get_thread(int_to_id(i))->is_complete()))
+ Thread *th = get_thread(int_to_id(i));
+ if ((last && rf->happens_before(last)) ||
+ !scheduler->is_enabled(th) ||
+ th->is_complete())
future_ordered = true;
+ ASSERT(!th->is_model_thread() || future_ordered);
+
for (rit = list->rbegin(); rit != list->rend(); rit++) {
const ModelAction *act = *rit;
/* Reach synchronization -> this thread is complete */
bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_queue)
{
bool updated = false;
- std::vector<struct release_seq *>::iterator it = pending_rel_seqs->begin();
+ std::vector< struct release_seq *, SnapshotAlloc<struct release_seq *> >::iterator it = pending_rel_seqs->begin();
while (it != pending_rel_seqs->end()) {
struct release_seq *pending = *it;
ModelAction *act = pending->acquire;
bool ModelChecker::resolve_promises(ModelAction *write)
{
bool resolved = false;
- std::vector<thread_id_t> threads_to_check;
+ std::vector< thread_id_t, ModelAlloc<thread_id_t> > threads_to_check;
for (unsigned int i = 0, promise_index = 0; promise_index < promises->size(); i++) {
Promise *promise = (*promises)[promise_index];
//do we have a pwrite for the promise, if not, set it
if (promise->get_write() == NULL ) {
promise->set_write(write);
+ //The pwrite cannot happen before the promise
+ if (write->happens_before(act) && (write != act)) {
+ failed_promise = true;
+ return;
+ }
}
if (mo_graph->checkPromise(write, promise)) {
failed_promise = true;
}
}
+/**
+ * Compute the set of writes that may break the current pending release
+ * sequence. This information is extracted from previou release sequence
+ * calculations.
+ *
+ * @param curr The current ModelAction. Must be a release sequence fixup
+ * action.
+ */
+void ModelChecker::compute_relseq_breakwrites(ModelAction *curr)
+{
+ if (pending_rel_seqs->empty())
+ return;
+
+ struct release_seq *pending = pending_rel_seqs->back();
+ for (unsigned int i = 0; i < pending->writes.size(); i++) {
+ const ModelAction *write = pending->writes[i];
+ curr->get_node()->add_relseq_break(write);
+ }
+
+ /* NULL means don't break the sequence; just synchronize */
+ curr->get_node()->add_relseq_break(NULL);
+}
+
/**
* Build up an initial set of all past writes that this 'read' action may read
* from. This set is determined by the clock vector's "happens before"
act->print();
curr->print();
}
- curr->get_node()->add_read_from(act);
+
+ if (curr->get_sleep_flag() && ! curr->is_seqcst()) {
+ if (sleep_can_read_from(curr, act))
+ curr->get_node()->add_read_from(act);
+ } else
+ curr->get_node()->add_read_from(act);
}
/* Include at most one act per-thread that "happens before" curr */
ASSERT(initialized);
}
+bool ModelChecker::sleep_can_read_from(ModelAction * curr, const ModelAction *write) {
+ while(true) {
+ Node *prevnode=write->get_node()->get_parent();
+ bool thread_sleep=prevnode->get_enabled_array()[id_to_int(curr->get_tid())]==THREAD_SLEEP_SET;
+ if (write->is_release()&&thread_sleep)
+ return true;
+ if (!write->is_rmw()) {
+ return false;
+ }
+ if (write->get_reads_from()==NULL)
+ return true;
+ write=write->get_reads_from();
+ }
+}
+
static void print_list(action_list_t *list)
{
action_list_t::iterator it;
printf("---------------------------------------------------------------------\n");
printf("Trace:\n");
-
+ 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");
}
if (has_asserted())
return false;
- Thread *curr = thread_current();
+ Thread *curr = priv->current_action ? get_thread(priv->current_action) : NULL;
if (curr) {
if (curr->get_state() == THREAD_READY) {
ASSERT(priv->current_action);
if (!isfeasible())
return false;
+ if (params.bound != 0) {
+ if (priv->used_sequence_numbers > params.bound) {
+ return false;
+ }
+ }
+
DEBUG("(%d, %d)\n", curr ? id_to_int(curr->get_id()) : -1,
next ? id_to_int(next->get_id()) : -1);
+ /*
+ * Launch end-of-execution release sequence fixups only when there are:
+ *
+ * (1) no more user threads to run (or when execution replay chooses
+ * the 'model_thread')
+ * (2) pending release sequences
+ * (3) pending assertions (i.e., data races)
+ * (4) no pending promises
+ */
+ if (!pending_rel_seqs->empty() && (!next || next->is_model_thread()) &&
+ isfinalfeasible() && !unrealizedraces.empty()) {
+ printf("*** 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,
+ model_thread);
+ set_current_action(fixup);
+ return true;
+ }
+
/* next == NULL -> don't take any more steps */
if (!next)
return false;