/* Copyright (C) 2008, 2009, 2011 Free Software Foundation, Inc. Contributed by Richard Henderson . 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 . */ #include #include #include #include "libitm_i.h" // The default TM method used when starting a new transaction. static GTM::abi_dispatch* default_dispatch = 0; // 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->get_method_group()) { // Still the same method group. disp->get_method_group()->fini(); disp->get_method_group()->init(); } serial_lock.write_unlock(); serial_lock.read_lock(this); if (disp->get_method_group() != default_dispatch->get_method_group()) { disp = default_dispatch; set_abi_disp(disp); } } else { // We are a serial transaction already, which makes things simple. disp->get_method_group()->fini(); disp->get_method_group()->init(); } } 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. GTM::abi_dispatch* GTM::gtm_thread::decide_begin_dispatch (uint32_t prop) { // TODO Pay more attention to prop flags (eg, *omitted) when selecting // dispatch. if ((prop & pr_doesGoIrrevocable) || !(prop & pr_instrumentedCode)) return dispatch_serialirr(); // 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) && !default_dispatch->closed_nesting() && default_dispatch->closed_nesting_alternative()) return default_dispatch->closed_nesting_alternative(); // No special case, just use the default dispatch. return default_dispatch; } void GTM::gtm_thread::set_default_dispatch(GTM::abi_dispatch* disp) { if (default_dispatch == disp) return; if (default_dispatch) { // If we are switching method groups, initialize and shut down properly. if (default_dispatch->get_method_group() != disp->get_method_group()) { default_dispatch->get_method_group()->fini(); disp->get_method_group()->init(); } } else disp->get_method_group()->init(); default_dispatch = disp; } 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 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_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) 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) set_default_dispatch(default_dispatch_user); else set_default_dispatch(dispatch_serialirr_onwrite()); } }