Files
mercury/runtime/mercury_stm.c
Mark Brown d465fa53cb Update the COPYING.LIB file and references to it.
Discussion of these changes can be found on the Mercury developers
mailing list archives from June 2018.

COPYING.LIB:
    Add a special linking exception to the LGPL.

*:
    Update references to COPYING.LIB.

    Clean up some minor errors that have accumulated in copyright
    messages.
2018-06-09 17:43:12 +10:00

404 lines
12 KiB
C

// vim: ts=4 sw=4 expandtab ft=c
// Copyright (C) 2007-2009 The University of Melbourne.
// Copyright (C) 2014, 2016, 2018 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.
// mercury_stm.c - runtime support for software transactional memory.
#include "mercury_std.h"
#include "mercury_stm.h"
#include "mercury_memory.h"
#include "mercury_misc.h"
#if defined(MR_THREAD_SAFE)
MercuryLock MR_STM_lock;
#endif
void
MR_STM_record_transaction(MR_STM_TransLog *tlog, MR_STM_Var *var,
MR_Word old_value, MR_Word new_value)
{
MR_STM_TransRecord *new_record;
new_record = MR_GC_NEW_ATTRIB(MR_STM_TransRecord,
MR_ALLOC_SITE_RUNTIME);
new_record->MR_STM_tr_var = var;
new_record->MR_STM_tr_old_value = old_value;
new_record->MR_STM_tr_new_value = new_value;
new_record->MR_STM_tr_next = tlog->MR_STM_tl_records;
tlog->MR_STM_tl_records = new_record;
}
void
MR_STM_attach_waiter(MR_STM_Var *var, MR_ThreadId tid,
MR_STM_ConditionVar *cvar)
{
MR_STM_Waiter *new_waiter;
new_waiter = MR_GC_NEW_ATTRIB(MR_STM_Waiter, MR_ALLOC_SITE_RUNTIME);
new_waiter->MR_STM_cond_var = cvar;
if (var->MR_STM_var_waiters == NULL) {
var->MR_STM_var_waiters = new_waiter;
new_waiter->MR_STM_waiter_prev = NULL;
new_waiter->MR_STM_waiter_next = NULL;
} else {
new_waiter->MR_STM_waiter_prev = NULL;
new_waiter->MR_STM_waiter_next = var->MR_STM_var_waiters;
var->MR_STM_var_waiters->MR_STM_waiter_prev = new_waiter;
var->MR_STM_var_waiters = new_waiter;
}
}
void
MR_STM_detach_waiter(MR_STM_Var *var, MR_STM_ConditionVar *cvar)
{
MR_STM_Waiter *curr_waiter;
MR_assert(var != NULL);
MR_assert(var->MR_STM_var_waiters != NULL);
curr_waiter = var->MR_STM_var_waiters;
while (curr_waiter != NULL) {
if (curr_waiter->MR_STM_cond_var == cvar) {
if (curr_waiter == var->MR_STM_var_waiters) {
var->MR_STM_var_waiters =
var->MR_STM_var_waiters->MR_STM_waiter_next;
}
if (curr_waiter->MR_STM_waiter_prev != NULL) {
curr_waiter->MR_STM_waiter_prev->MR_STM_waiter_next =
curr_waiter->MR_STM_waiter_next;
}
if (curr_waiter->MR_STM_waiter_next != NULL) {
curr_waiter->MR_STM_waiter_next->MR_STM_waiter_prev =
curr_waiter->MR_STM_waiter_prev;
}
curr_waiter = NULL;
return;
}
curr_waiter = curr_waiter->MR_STM_waiter_next;
}
MR_fatal_error("MR_STM_detach_waiter: Thread ID not in wait queue");
}
MR_Integer
MR_STM_validate(MR_STM_TransLog *tlog)
{
MR_STM_TransRecord *current;
MR_assert(tlog != NULL);
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM VALIDATE: validating log <0x%.8lx>\n",
(MR_Word) tlog);
fprintf(stderr, "\tRecords: <0x%.8lx>\n",
(MR_Word) tlog->MR_STM_tl_records);
#endif
while (tlog != NULL) {
current = tlog->MR_STM_tl_records;
while (current != NULL) {
if (current->MR_STM_tr_var->MR_STM_var_value !=
current->MR_STM_tr_old_value)
{
#if defined(MR_STM_DEBUG)
fprintf(stderr, "\ttransaction INVALID.\n");
#endif
return MR_STM_TRANSACTION_INVALID;
}
current = current->MR_STM_tr_next;
}
tlog = tlog->MR_STM_tl_parent;
}
#if defined(MR_STM_DEBUG)
fprintf(stderr, "\ttransaction VALID.\n");
#endif
return MR_STM_TRANSACTION_VALID;
}
void
MR_STM_signal_vars(MR_STM_Var *tvar)
{
MR_STM_Waiter *wait_queue;
wait_queue = tvar->MR_STM_var_waiters;
while (wait_queue != NULL) {
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM SIGNAL: signalling log <0x%.8lx>\n",
(MR_Word) wait_queue->MR_STM_cond_var);
#endif
MR_STM_condvar_signal(wait_queue->MR_STM_cond_var);
wait_queue = wait_queue->MR_STM_waiter_next;
}
}
void
MR_STM_commit(MR_STM_TransLog *tlog)
{
MR_STM_TransRecord *current;
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM COMMIT: committing log <0x%.8lx>\n",
(MR_Word) tlog);
#endif
current = tlog->MR_STM_tl_records;
while (current != NULL) {
#if defined(MR_STM_DEBUG)
fprintf(stderr,
"\tSTM_Var <%.8lx>, changing value from %ld to %ld\n",
(MR_Word) current->MR_STM_tr_var,
current->MR_STM_tr_var->MR_STM_var_value,
current->MR_STM_tr_new_value);
#endif
current->MR_STM_tr_var->MR_STM_var_value =
current->MR_STM_tr_new_value;
MR_STM_signal_vars(current->MR_STM_tr_var);
current = current->MR_STM_tr_next;
}
}
void
MR_STM_wait(MR_STM_TransLog *tlog, MR_STM_ConditionVar *cvar)
{
MR_STM_TransRecord *current;
MR_ThreadId this_thread_id;
this_thread_id = MR_THIS_THREAD_ID;
current = tlog->MR_STM_tl_records;
while (current != NULL) {
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM WAIT: attaching waiter on log <0x%.8lx>\n",
(MR_Word) tlog);
fprintf(stderr, "\tSTM_Var: <0x%.8lx>\n",
(MR_Word) current->MR_STM_tr_var);
#endif
MR_STM_attach_waiter(current->MR_STM_tr_var, this_thread_id, cvar);
current = current->MR_STM_tr_next;
}
}
void
MR_STM_unwait(MR_STM_TransLog *tlog, MR_STM_ConditionVar *cvar)
{
MR_STM_TransRecord *current;
MR_ThreadId this_thread_id;
this_thread_id = MR_THIS_THREAD_ID;
current = tlog->MR_STM_tl_records;
while (current != NULL) {
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM UNWAIT: detaching waiter on log <0x%.8lx>\n",
(MR_Word) tlog);
fprintf(stderr, "\tSTM_Var: <0x%.8lx>\n",
(MR_Word) current->MR_STM_tr_var);
#endif
MR_STM_detach_waiter(current->MR_STM_tr_var, cvar);
current = current->MR_STM_tr_next;
}
}
void
MR_STM_unsafe_write_var(MR_STM_Var *var, MR_Word value)
{
#if defined(MR_STM_DEBUG)
fprintf(stderr, "UNSAFE_WRITE_VAR:\n");
fprintf(stderr, "\tSTM_Var <%.8lx>, changing value from %ld to %ld\n",
(MR_Word) var, var->MR_STM_var_value, value);
#endif
var->MR_STM_var_value = value;
}
void
MR_STM_write_var(MR_STM_Var *var, MR_Word value, MR_STM_TransLog *tlog)
{
MR_STM_TransRecord *current;
MR_bool has_existing_record = MR_FALSE;
// Check to see if this transaction variable has an existing record in
// transaction log; if so, update it.
current = tlog->MR_STM_tl_records;
while (current != NULL) {
if (current->MR_STM_tr_var == var) {
has_existing_record = MR_TRUE;
current->MR_STM_tr_new_value = value;
break;
}
current = current->MR_STM_tr_next;
}
// Add a new entry for the transaction variable if didn't already have one.
if (!has_existing_record) {
MR_STM_record_transaction(tlog, var, var->MR_STM_var_value, value);
}
}
MR_Word
MR_STM_read_var(MR_STM_Var *var, MR_STM_TransLog *tlog)
{
MR_STM_TransLog *current_tlog;
MR_STM_TransRecord *current;
current_tlog = tlog;
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM Read: Log <%.8lx> -- var <%.8lx>\n",
(MR_Word) tlog, (MR_Word) var);
#endif
while (current_tlog != NULL) {
current = current_tlog->MR_STM_tl_records;
while (current != NULL) {
if (current->MR_STM_tr_var == var) {
return current->MR_STM_tr_new_value;
}
current = current->MR_STM_tr_next;
}
current_tlog = current_tlog->MR_STM_tl_parent;
}
// We will only get to this point if the transaction variable does not
// currently have a record in the log or one of the enclosing logs,
// i.e. if this is the first time that its value has been read during
// this atomic scope.
// Add an entry that indicates that it has been read and then return
// the value that is stored in the transaction variable.
MR_STM_record_transaction(tlog, var, var->MR_STM_var_value,
var->MR_STM_var_value);
return var->MR_STM_var_value;
}
void
MR_STM_merge_transactions(MR_STM_TransLog *tlog)
{
MR_STM_TransLog *parent_log;
MR_STM_TransRecord *parent_current;
MR_STM_TransRecord *current;
MR_bool found_tvar_in_parent;
MR_assert(tlog != NULL);
MR_assert(tlog->MR_STM_tl_parent != NULL);
parent_log = tlog->MR_STM_tl_parent;
current = tlog->MR_STM_tl_records;
while (current != NULL) {
found_tvar_in_parent = MR_NO;
parent_current = parent_log->MR_STM_tl_records;
while (parent_current != NULL) {
if (current->MR_STM_tr_var == parent_current->MR_STM_tr_var) {
parent_current->MR_STM_tr_new_value =
current->MR_STM_tr_new_value;
found_tvar_in_parent = MR_YES;
break;
}
parent_current = parent_current->MR_STM_tr_next;
}
if (! found_tvar_in_parent) {
MR_STM_record_transaction(parent_log,
current->MR_STM_tr_var, current->MR_STM_tr_old_value,
current->MR_STM_tr_new_value);
}
current = current->MR_STM_tr_next;
}
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM: Merging log end: <0x%.8lx>\n",
(MR_Word) tlog);
#endif
// Deallocate child log.
#if !defined(MR_CONSERVATIVE_GC)
// XXX -- Free tlog and log entries.
#endif
}
#if defined(MR_HIGHLEVEL_CODE)
// MR_STM_block_thread is called to block the thread in high level C grades,
// using POSIX thread facilities, as there is a POSIX thread for every
// Mercury thread in these grades. The low level C grade equivalent of this
// code is defined in the stm_builtin library module.
void
MR_STM_block_thread(MR_STM_TransLog *tlog)
{
#if defined(MR_THREAD_SAFE)
MR_STM_ConditionVar *thread_condvar;
thread_condvar = MR_GC_NEW_ATTRIB(MR_STM_ConditionVar,
MR_ALLOC_SITE_RUNTIME);
MR_STM_condvar_init(thread_condvar);
MR_STM_wait(tlog, thread_condvar);
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM BLOCKING: log <0x%.8lx>\n", (MR_Word)tlog);
#endif
MR_STM_condvar_wait(thread_condvar, &MR_STM_lock);
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM RESCHEDULING: log <0x%.8lx>\n", (MR_Word)tlog);
#endif
MR_STM_unwait(tlog, thread_condvar);
MR_UNLOCK(&MR_STM_lock, "MR_STM_block_thread");
MR_GC_free_attrib(thread_condvar);
#else
MR_fatal_error("Blocking thread in non-parallel grade");
#endif
}
#endif // MR_HIGHLEVEL_CODE
#if !defined(MR_HIGHLEVEL_CODE)
// In the low level C grades, the "condition variable" created when an STM
// transaction blocks is actually a pointer to the transaction log.
// "Signalling" it consists of going through the STM variables listed in the
// log and removing the waiters attached to them for the context listed
// in the log. After this, the context can be safely rescheduled.
void
MR_STM_condvar_signal(MR_STM_ConditionVar *cvar)
{
// Calling MR_STM_unwait here should be safe, as this signalling is called
// in response to a commit, while the committing thread holds the global
// STM lock. Note that a MR_STM_ConditionVar IS a MR_STM_TransLog if
// MR_HIGHLEVEL_CODE is not defined, which is why cvar is passed twice.
MR_STM_unwait(cvar, cvar);
#if defined(MR_STM_DEBUG)
fprintf(stderr, "STM RESCHEDULING: log <0x%.8lx>\n", (MR_Word)cvar);
#endif
MR_schedule_context(MR_STM_context_from_condvar(cvar));
}
#endif // !MR_HIGHLEVEL_CODE