mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			336 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* Copyright (C) 2008-2019 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());
 | |
| 	}
 | |
|     }
 | |
| }
 |