mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-09 19:02:18 +00:00
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.
404 lines
12 KiB
C
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
|