gcc/libitm/retry.cc
2024-01-03 12:19:35 +01:00

336 lines
11 KiB
C++

/* Copyright (C) 2008-2024 Free Software Foundation, Inc.
Contributed by Richard Henderson <rth@redhat.com>.
This file is part of the GNU Transactional Memory Library (libitm).
Libitm is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
Libitm is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "libitm_i.h"
// The default TM method used when starting a new transaction. Initialized
// in number_of_threads_changed() below.
// Access to this variable is always synchronized with help of the serial
// lock, except one read access that happens in decide_begin_dispatch() before
// a transaction has become active (by acquiring the serial lock in read or
// write mode). The default_dispatch is only changed and initialized in
// serial mode. Transactions stay active when they restart (see beginend.cc),
// thus decide_retry_strategy() can expect default_dispatch to be unmodified.
// See decide_begin_dispatch() for further comments.
static std::atomic<GTM::abi_dispatch*> default_dispatch;
// The default TM method as requested by the user, if any.
static GTM::abi_dispatch* default_dispatch_user = 0;
void
GTM::gtm_thread::decide_retry_strategy (gtm_restart_reason r)
{
struct abi_dispatch *disp = abi_disp ();
this->restart_reason[r]++;
this->restart_total++;
if (r == RESTART_INIT_METHOD_GROUP)
{
// A re-initializations of the method group has been requested. Switch
// to serial mode, initialize, and resume normal operation.
if ((state & STATE_SERIAL) == 0)
{
// We have to eventually re-init the method group. Therefore,
// we cannot just upgrade to a write lock here because this could
// fail forever when other transactions execute in serial mode.
// However, giving up the read lock then means that a change of the
// method group could happen in-between, so check that we're not
// re-initializing without a need.
// ??? Note that we can still re-initialize too often, but avoiding
// that would increase code complexity, which seems unnecessary
// given that re-inits should be very infrequent.
serial_lock.read_unlock(this);
serial_lock.write_lock();
if (disp->get_method_group()
== default_dispatch.load(memory_order_relaxed)
->get_method_group())
// Still the same method group.
disp->get_method_group()->reinit();
serial_lock.write_unlock();
// Also, we're making the transaction inactive, so when we become
// active again, some other thread might have changed the default
// dispatch, so we run the same code as for the first execution
// attempt.
disp = decide_begin_dispatch(prop);
set_abi_disp(disp);
}
else
// We are a serial transaction already, which makes things simple.
disp->get_method_group()->reinit();
return;
}
bool retry_irr = (r == RESTART_SERIAL_IRR);
bool retry_serial = (retry_irr || this->restart_total > 100);
// We assume closed nesting to be infrequently required, so just use
// dispatch_serial (with undo logging) if required.
if (r == RESTART_CLOSED_NESTING)
retry_serial = true;
if (retry_serial)
{
// In serialirr_mode we can succeed with the upgrade to
// write-lock but fail the trycommit. In any case, if the
// write lock is not yet held, grab it. Don't do this with
// an upgrade, since we've no need to preserve the state we
// acquired with the read.
// Note that we will be restarting with either dispatch_serial or
// dispatch_serialirr, which are compatible with all TM methods; if
// we would retry with a different method, we would have to first check
// whether the default dispatch or the method group have changed. Also,
// the caller must have rolled back the previous transaction, so we
// don't have to worry about things such as privatization.
if ((this->state & STATE_SERIAL) == 0)
{
this->state |= STATE_SERIAL;
serial_lock.read_unlock (this);
serial_lock.write_lock ();
}
// We can retry with dispatch_serialirr if the transaction
// doesn't contain an abort and if we don't need closed nesting.
if ((this->prop & pr_hasNoAbort) && (r != RESTART_CLOSED_NESTING))
retry_irr = true;
}
// Note that we can just use serial mode here without having to switch
// TM method sets because serial mode is compatible with all of them.
if (retry_irr)
{
this->state = (STATE_SERIAL | STATE_IRREVOCABLE);
disp = dispatch_serialirr ();
set_abi_disp (disp);
}
else if (retry_serial)
{
disp = dispatch_serial();
set_abi_disp (disp);
}
}
// Decides which TM method should be used on the first attempt to run this
// transaction. Acquires the serial lock and sets transaction state
// according to the chosen TM method.
GTM::abi_dispatch*
GTM::gtm_thread::decide_begin_dispatch (uint32_t prop)
{
abi_dispatch* dd;
// TODO Pay more attention to prop flags (eg, *omitted) when selecting
// dispatch.
// ??? We go irrevocable eagerly here, which is not always good for
// performance. Don't do this?
if ((prop & pr_doesGoIrrevocable) || !(prop & pr_instrumentedCode))
dd = dispatch_serialirr();
else
{
// Load the default dispatch. We're not an active transaction and so it
// can change concurrently but will still be some valid dispatch.
// Relaxed memory order is okay because we expect each dispatch to be
// constructed properly already (at least that its closed_nesting() and
// closed_nesting_alternatives() will return sensible values). It is
// harmless if we incorrectly chose the serial or serialirr methods, and
// for all other methods we will acquire the serial lock in read mode
// and load the default dispatch again.
abi_dispatch* dd_orig = default_dispatch.load(memory_order_relaxed);
dd = dd_orig;
// If we might need closed nesting and the default dispatch has an
// alternative that supports closed nesting, use it.
// ??? We could choose another TM method that we know supports closed
// nesting but isn't the default (e.g., dispatch_serial()). However, we
// assume that aborts that need closed nesting are infrequent, so don't
// choose a non-default method until we have to actually restart the
// transaction.
if (!(prop & pr_hasNoAbort) && !dd->closed_nesting()
&& dd->closed_nesting_alternative())
dd = dd->closed_nesting_alternative();
if (!(dd->requires_serial() & STATE_SERIAL))
{
// The current dispatch is supposedly a non-serial one. Become an
// active transaction and verify this. Relaxed memory order is fine
// because the serial lock itself will have established
// happens-before for any change to the selected dispatch.
serial_lock.read_lock (this);
if (default_dispatch.load(memory_order_relaxed) == dd_orig)
return dd;
// If we raced with a concurrent modification of default_dispatch,
// just fall back to serialirr. The dispatch choice might not be
// up-to-date anymore, but this is harmless.
serial_lock.read_unlock (this);
dd = dispatch_serialirr();
}
}
// We are some kind of serial transaction.
serial_lock.write_lock();
state = dd->requires_serial();
return dd;
}
void
GTM::gtm_thread::set_default_dispatch(GTM::abi_dispatch* disp)
{
abi_dispatch* dd = default_dispatch.load(memory_order_relaxed);
if (dd == disp)
return;
if (dd)
{
// If we are switching method groups, initialize and shut down properly.
if (dd->get_method_group() != disp->get_method_group())
{
dd->get_method_group()->fini();
disp->get_method_group()->init();
}
}
else
disp->get_method_group()->init();
default_dispatch.store(disp, memory_order_relaxed);
}
static GTM::abi_dispatch*
parse_default_method()
{
const char *env = getenv("ITM_DEFAULT_METHOD");
GTM::abi_dispatch* disp = 0;
if (env == NULL)
return 0;
while (isspace((unsigned char) *env))
++env;
if (strncmp(env, "serialirr_onwrite", 17) == 0)
{
disp = GTM::dispatch_serialirr_onwrite();
env += 17;
}
else if (strncmp(env, "serialirr", 9) == 0)
{
disp = GTM::dispatch_serialirr();
env += 9;
}
else if (strncmp(env, "serial", 6) == 0)
{
disp = GTM::dispatch_serial();
env += 6;
}
else if (strncmp(env, "gl_wt", 5) == 0)
{
disp = GTM::dispatch_gl_wt();
env += 5;
}
else if (strncmp(env, "ml_wt", 5) == 0)
{
disp = GTM::dispatch_ml_wt();
env += 5;
}
else if (strncmp(env, "htm", 3) == 0)
{
disp = GTM::dispatch_htm();
env += 3;
}
else
goto unknown;
while (isspace((unsigned char) *env))
++env;
if (*env == '\0')
return disp;
unknown:
GTM::GTM_error("Unknown TM method in environment variable "
"ITM_DEFAULT_METHOD\n");
return 0;
}
// Gets notifications when the number of registered threads changes. This is
// used to initialize the method set choice and trigger straightforward choice
// adaption.
// This must be called only by serial threads.
void
GTM::gtm_thread::number_of_threads_changed(unsigned previous, unsigned now)
{
if (previous == 0)
{
// No registered threads before, so initialize.
static bool initialized = false;
if (!initialized)
{
initialized = true;
// Check for user preferences here.
default_dispatch = 0;
default_dispatch_user = parse_default_method();
}
}
else if (now == 0)
{
// No registered threads anymore. The dispatch based on serial mode do
// not have any global state, so this effectively shuts down properly.
set_default_dispatch(dispatch_serialirr());
}
if (now == 1)
{
// Only one thread, so use a serializing method.
// ??? If we don't have a fast serial mode implementation, it might be
// better to use the global lock method set here.
if (default_dispatch_user && default_dispatch_user->supports(now))
set_default_dispatch(default_dispatch_user);
else
set_default_dispatch(dispatch_serialirr());
}
else if (now > 1 && previous <= 1)
{
// More than one thread, use the default method.
if (default_dispatch_user && default_dispatch_user->supports(now))
set_default_dispatch(default_dispatch_user);
else
{
// If HTM is available, use it by default with serial mode as
// fallback. Otherwise, use ml_wt because it probably scales best.
abi_dispatch* a;
#ifdef USE_HTM_FASTPATH
if (htm_available())
a = dispatch_htm();
else
#endif
a = dispatch_ml_wt();
if (a->supports(now))
set_default_dispatch(a);
else
// Serial-irrevocable mode always works.
set_default_dispatch(dispatch_serialirr());
}
}
}