Patchwork [trans-mem] Add method groups and change TM method lifecycle and selection.

login
register
mail settings
Submitter Torvald Riegel
Date Aug. 23, 2011, 12:01 p.m.
Message ID <1314100903.3533.3489.camel@triegel.csb>
Download mbox | patch
Permalink /patch/111078/
State New
Headers show

Comments

Torvald Riegel - Aug. 23, 2011, 12:01 p.m.
The patch adds method groups for TM methods, which group methods that
can run concurrently together. The lifecycle and state management
responsibilities of method groups and methods get documented.

For now, there is just a method group for all serial methods, more will
follow when further TM methods are added.

A new default dispatch (aka method) is maintained, and there is a very
simple runtime adaption scheme that uses dispatch_serialirr() if there
is just one registered thread, and the default dispatch (currently
serialirr_onwrite) if there is more than one. The user can override this
by specifying a dispatch via the ITM_DEFAULT_METHOD environment
variable, which then makes libitm always use this method.

This should be the last major step of the refactoring. Future patches
can now add further TM methods, and those methods should fit in more
easily.

OK for branch?
commit 431f3c067873e41f536022f6bcfc307464bf91fd
Author: Torvald Riegel <triegel@redhat.com>
Date:   Tue Aug 23 13:44:19 2011 +0200

    Add method groups and change TM method lifecycle and selection.
    
    	* retry.cc (GTM::gtm_thread::decide_retry_strategy): Cleanup. Fix
    	restarting without switching to serial mode.
    	(GTM::gtm_thread::decide_begin_dispatch): Let the caller set the
    	transaction state. Choose closed-nesting alternative if available.
    	(GTM::gtm_thread::set_default_dispatch): New.
    	(parse_default_method): New.
    	(GTM::gtm_thread::number_of_threads_changed): New.
    	* method-serial.cc (GTM::serial_mg): New method group class.
    	(GTM::serialirr_dispatch): Belongs to serial_mg. Remove reinit and
    	fini.
    	(GTM::serial_dispatch): Same.
    	(GTM::serialirr_onwrite_dispatch): Same.
    	(GTM::gtm_thread::serialirr_mode): Remove calls to fini.
    	* beginend.cc (GTM::gtm_thread::~gtm_thread): Maintain number of
    	registered threads.
    	(GTM::gtm_thread::gtm_thread): Same.
    	(_ITM_abortTransaction): Remove calls to abi_dispatch::fini().
    	(GTM::gtm_thread::trycommit): Same. Reset number of restarts.
    	(GTM::gtm_thread::begin_transaction): Let decide_begin_dispatch()
    	choose dispatch but set state according to dispatch here.
    	* dispatch.h (GTM::abi_dispatch::fini): Move to method group.
    	(GTM::method_group): New class.
    	(GTM::abi_dispatch): Add comments. Maintain pointer to method_group.
    	* libitm_i.h (GTM::gtm_thread): Add declarations for new members.
    	* libitm.texi: Document TM methods, method groups, method life cycle.
    	Rename method sets to method groups.
Richard Henderson - Aug. 23, 2011, 4:53 p.m.
On 08/23/2011 05:01 AM, Torvald Riegel wrote:
>     Add method groups and change TM method lifecycle and selection.
>     
>     	* retry.cc (GTM::gtm_thread::decide_retry_strategy): Cleanup. Fix
>     	restarting without switching to serial mode.
>     	(GTM::gtm_thread::decide_begin_dispatch): Let the caller set the
>     	transaction state. Choose closed-nesting alternative if available.
>     	(GTM::gtm_thread::set_default_dispatch): New.
>     	(parse_default_method): New.
>     	(GTM::gtm_thread::number_of_threads_changed): New.
>     	* method-serial.cc (GTM::serial_mg): New method group class.
>     	(GTM::serialirr_dispatch): Belongs to serial_mg. Remove reinit and
>     	fini.
>     	(GTM::serial_dispatch): Same.
>     	(GTM::serialirr_onwrite_dispatch): Same.
>     	(GTM::gtm_thread::serialirr_mode): Remove calls to fini.
>     	* beginend.cc (GTM::gtm_thread::~gtm_thread): Maintain number of
>     	registered threads.
>     	(GTM::gtm_thread::gtm_thread): Same.
>     	(_ITM_abortTransaction): Remove calls to abi_dispatch::fini().
>     	(GTM::gtm_thread::trycommit): Same. Reset number of restarts.
>     	(GTM::gtm_thread::begin_transaction): Let decide_begin_dispatch()
>     	choose dispatch but set state according to dispatch here.
>     	* dispatch.h (GTM::abi_dispatch::fini): Move to method group.
>     	(GTM::method_group): New class.
>     	(GTM::abi_dispatch): Add comments. Maintain pointer to method_group.
>     	* libitm_i.h (GTM::gtm_thread): Add declarations for new members.
>     	* libitm.texi: Document TM methods, method groups, method life cycle.
>     	Rename method sets to method groups.

Ok.


r~

Patch

diff --git a/libitm/beginend.cc b/libitm/beginend.cc
index e53ea6c..cc25d17 100644
--- a/libitm/beginend.cc
+++ b/libitm/beginend.cc
@@ -34,6 +34,7 @@  extern __thread gtm_thread_tls _gtm_thr_tls;
 
 gtm_rwlock GTM::gtm_thread::serial_lock;
 gtm_thread *GTM::gtm_thread::list_of_threads = 0;
+unsigned GTM::gtm_thread::number_of_threads = 0;
 
 gtm_stmlock GTM::gtm_stmlock_array[LOCK_ARRAY_SIZE];
 gtm_version GTM::gtm_clock;
@@ -103,6 +104,8 @@  GTM::gtm_thread::~gtm_thread()
           break;
         }
     }
+  number_of_threads--;
+  number_of_threads_changed(number_of_threads + 1, number_of_threads);
   serial_lock.write_unlock ();
 }
 
@@ -117,6 +120,8 @@  GTM::gtm_thread::gtm_thread ()
   serial_lock.write_lock ();
   next_thread = list_of_threads;
   list_of_threads = this;
+  number_of_threads++;
+  number_of_threads_changed(number_of_threads - 1, number_of_threads);
   serial_lock.write_unlock ();
 
   if (pthread_once(&thr_release_once, thread_exit_init))
@@ -226,27 +231,16 @@  GTM::gtm_thread::begin_transaction (uint32_t prop, const gtm_jmpbuf *jb)
   else
     {
       // Outermost transaction
-      // TODO Pay more attention to prop flags (eg, *omitted) when selecting
-      // dispatch.
-      if ((prop & pr_doesGoIrrevocable) || !(prop & pr_instrumentedCode))
-        tx->state = (STATE_SERIAL | STATE_IRREVOCABLE);
-
-      else
-        disp = tx->decide_begin_dispatch (prop);
-
-      if (tx->state & STATE_SERIAL)
+      disp = tx->decide_begin_dispatch (prop);
+      if (disp == dispatch_serialirr() || disp == dispatch_serial())
         {
+          tx->state = STATE_SERIAL;
+          if (disp == dispatch_serialirr())
+            tx->state |= STATE_IRREVOCABLE;
           serial_lock.write_lock ();
-
-          if (tx->state & STATE_IRREVOCABLE)
-            disp = dispatch_serialirr ();
-          else
-            disp = dispatch_serial ();
         }
       else
-        {
-          serial_lock.read_lock (tx);
-        }
+        serial_lock.read_lock (tx);
 
       set_abi_disp (disp);
     }
@@ -387,7 +381,6 @@  _ITM_abortTransaction (_ITM_abortReason reason)
       gtm_jmpbuf longjmp_jb = tx->jb;
 
       tx->rollback (cp);
-      abi_disp()->fini ();
 
       // Jump to nested transaction (use the saved jump buffer).
       GTM_longjmp (&longjmp_jb, a_abortTransaction | a_restoreLiveVariables,
@@ -397,7 +390,6 @@  _ITM_abortTransaction (_ITM_abortReason reason)
     {
       // There is no nested transaction, so roll back to outermost transaction.
       tx->rollback ();
-      abi_disp()->fini ();
 
       // Aborting an outermost transaction finishes execution of the whole
       // transaction. Therefore, reset transaction state.
@@ -439,11 +431,11 @@  GTM::gtm_thread::trycommit ()
       // FIXME: run after ensuring privatization safety:
       commit_user_actions ();
       commit_allocations (false, 0);
-      abi_disp()->fini ();
 
       // Reset transaction state.
       cxa_catch_count = 0;
       cxa_unthrown = NULL;
+      restart_total = 0;
 
       // TODO can release SI mode before committing user actions? If so,
       // we can release before ensuring privatization safety too.
diff --git a/libitm/dispatch.h b/libitm/dispatch.h
index 0ab5c4e..9c33684 100644
--- a/libitm/dispatch.h
+++ b/libitm/dispatch.h
@@ -236,8 +236,19 @@  namespace GTM HIDDEN {
 
 struct gtm_transaction_cp;
 
-// This pass-through method is the basis for other methods.
-// It can be used for serial-irrevocable mode.
+struct method_group
+{
+  // Start using a TM method from this group. This constructs required meta
+  // data on demand when this method group is actually used. Will be called
+  // either on first use or after a previous call to fini().
+  virtual void init() = 0;
+  // Stop using any method from this group for now. This can be used to
+  // destruct meta data as soon as this method group is not used anymore.
+  virtual void fini() = 0;
+};
+
+
+// This is the base interface that all TM methods have to implement.
 struct abi_dispatch
 {
 public:
@@ -249,16 +260,23 @@  private:
   abi_dispatch& operator=(const abi_dispatch &) = delete;
 
 public:
+  // Tries to commit the transaction. Iff this returns true, the transaction
+  // got committed and all per-transaction data will have been reset.
+  // Currently, this is called only for the commit of the outermost
+  // transaction, or when switching to serial mode (which can happen in a
+  // nested transaction).
+  // If the current transaction is in serial or serial-irrevocable mode, this
+  // must return true.
   virtual bool trycommit() = 0;
+  // Rolls back a transaction. Called on abort or after trycommit() returned
+  // false.
   virtual void rollback(gtm_transaction_cp *cp = 0) = 0;
-  virtual void reinit() = 0;
-
-  // Use fini instead of dtor to support a static subclasses that uses
-  // a unique object and so we don't want to destroy it from common code.
-  virtual void fini() = 0;
 
   // Return an alternative method that is compatible with the current
   // method but supports closed nesting. Return zero if there is none.
+  // Note that too be compatible, it must be possible to switch to this other
+  // method on begin of a nested transaction without committing or restarting
+  // the parent method.
   virtual abi_dispatch* closed_nesting_alternative() { return 0; }
 
   bool read_only () const { return m_read_only; }
@@ -267,7 +285,9 @@  public:
   {
     return m_can_run_uninstrumented_code;
   }
+  // Returns true iff this TM method supports closed nesting.
   bool closed_nesting() const { return m_closed_nesting; }
+  method_group* get_method_group() const { return m_method_group; }
 
   static void *operator new(size_t s) { return xmalloc (s); }
   static void operator delete(void *p) { free (p); }
@@ -288,10 +308,12 @@  protected:
   const bool m_write_through;
   const bool m_can_run_uninstrumented_code;
   const bool m_closed_nesting;
-  abi_dispatch(bool ro, bool wt, bool uninstrumented, bool closed_nesting) :
+  method_group* const m_method_group;
+  abi_dispatch(bool ro, bool wt, bool uninstrumented, bool closed_nesting,
+      method_group* mg) :
     m_read_only(ro), m_write_through(wt),
     m_can_run_uninstrumented_code(uninstrumented),
-    m_closed_nesting(closed_nesting)
+    m_closed_nesting(closed_nesting), m_method_group(mg)
   { }
 };
 
diff --git a/libitm/libitm.texi b/libitm/libitm.texi
index 6e4f8a0..3f57d1a 100644
--- a/libitm/libitm.texi
+++ b/libitm/libitm.texi
@@ -422,6 +422,47 @@  specification. Likewise, the TM runtime must ensure privatization safety.
 @node Internals
 @chapter Internals
 
+@section TM methods and method groups
+
+libitm supports several ways of synchronizing transactions with each other.
+These TM methods (or TM algorithms) are implemented in the form of
+subclasses of @code{abi_dispatch}, which provide methods for
+transactional loads and stores as well as callbacks for rollback and commit.
+All methods that are compatible with each other (i.e., that let concurrently
+running transactions still synchronize correctly even if different methods
+are used) belong to the same TM method group. Pointers to TM methods can be
+obtained using the factory methods prefixed with @code{dispatch_} in
+@file{libitm_i.h}. There are two special methods, @code{dispatch_serial} and
+@code{dispatch_serialirr}, that are compatible with all methods because they
+run transactions completely in serial mode.
+
+@subsection TM method life cycle
+
+The state of TM methods does not change after construction, but they do alter
+the state of transactions that use this method. However, because
+per-transaction data gets used by several methods, @code{gtm_thread} is
+responsible for setting an initial state that is useful for all methods.
+After that, methods are responsible for resetting/clearing this state on each
+rollback or commit (of outermost transactions), so that the transaction
+executed next is not affected by the previous transaction.
+
+There is also global state associated with each method group, which is
+initialized and shut down (@code{method_group::init()} and @code{fini()})
+when switching between method groups (see @file{retry.cc}).
+
+@subsection Selecting the default method
+
+The default method that libitm uses for freshly started transactions (but
+not necessarily for restarted transactions) can be set via an environment
+variable (@env{ITM_DEFAULT_METHOD}), whose value should be equal to the name
+of one of the factory methods returning abi_dispatch subclasses but without
+the "dispatch_" prefix (e.g., "serialirr" instead of
+@code{GTM::dispatch_serialirr()}).
+
+Note that this environment variable is only a hint for libitm and might not
+be supported in the future.
+
+
 @section Nesting: flat vs. closed
 
 We support two different kinds of nesting of transactions. In the case of
@@ -453,8 +494,8 @@  in libitm. We have to support serial(-irrevocable) mode, which is implemented
 using a global lock as explained next (called the @emph{serial lock}). To
 simplify the overall design, we use the same lock as catch-all locking
 mechanism for other infrequent tasks such as (de)registering clone tables or
-threads. Besides the serial lock, there are @emph{per-method-set locks} that
-are managed by specific method sets (i.e., groups of similar TM concurrency
+threads. Besides the serial lock, there are @emph{per-method-group locks} that
+are managed by specific method groups (i.e., groups of similar TM concurrency
 control algorithms), and lock-like constructs for quiescence-based operations
 such as ensuring privatization safety.
 
@@ -476,30 +517,30 @@  transaction.
 
 Application data is protected by the serial lock if there is a serial
 transaction and no concurrently running active transaction (i.e., non-serial).
-Otherwise, application data is protected by the currently selected method set,
-which might use per-method-set locks or other mechanisms. Also note that
-application data that is about to be privatized might not be allowed to be
+Otherwise, application data is protected by the currently selected method
+group, which might use per-method-group locks or other mechanisms. Also note
+that application data that is about to be privatized might not be allowed to be
 accessed by nontransactional code until privatization safety has been ensured;
-the details of this are handled by the current method set.
+the details of this are handled by the current method group.
 
 libitm-internal state is either protected by the serial lock or accessed
 through custom concurrent code. The latter applies to the public/shared part
-of a transaction object and most typical method-set-specific state.
+of a transaction object and most typical method-group-specific state.
 
 The former category (protected by the serial lock) includes:
 @itemize @bullet
 @item The list of active threads that have used transactions.
 @item The tables that map functions to their transactional clones.
-@item The current selection of which method set to use.
-@item Some method-set-specific data, or invariants of this data. For example,
-resetting a method set to its initial state is handled by switching to the
-same method set, so the serial lock protects such resetting as well.
+@item The current selection of which method group to use.
+@item Some method-group-specific data, or invariants of this data. For example,
+resetting a method group to its initial state is handled by switching to the
+same method group, so the serial lock protects such resetting as well.
 @end itemize
 In general, such state is immutable whenever there exists an active
 (non-serial) transaction. If there is no active transaction, a serial
 transaction (or a thread that is not currently executing a transaction but has
 acquired the serial lock) is allowed to modify this state (but must of course
-be careful to not surprise the current method set's implementation with such
+be careful to not surprise the current method group's implementation with such
 modifications).
 
 @subsection Lock acquisition order
@@ -510,13 +551,13 @@  necessarily apply to lock acquisitions that do not block (e.g., trylock()
 calls that do not get retried forever). Note that serial transactions are
 never return back to active transactions until the transaction has committed.
 Likewise, active transactions stay active until they have committed.
-Per-method-set locks are typically also not released before commit.
+Per-method-group locks are typically also not released before commit.
 
 Lock acquisition / blocking rules:
 @itemize @bullet
 
 @item Transactions must become active or serial before they are allowed to
-use method-set-specific locks or blocking (i.e., the serial lock must be
+use method-group-specific locks or blocking (i.e., the serial lock must be
 acquired before those other locks, either in serial or nonserial mode).
 
 @item Any number of threads that do not currently run active transactions can
@@ -525,35 +566,35 @@  transactions must not block when trying to upgrade to serial mode unless there
 is no other transaction that is trying that (the latter is ensured by the
 serial lock implementation.
 
-@item Method sets must prevent deadlocks on their locks. In particular, they
+@item Method groups must prevent deadlocks on their locks. In particular, they
 must also be prepared for another active transaction that has acquired
-method-set-specific locks but is blocked during an attempt to upgrade to
+method-group-specific locks but is blocked during an attempt to upgrade to
 being a serial transaction. See below for details.
 
-@item Serial transactions can acquire method-set-specific locks because there
+@item Serial transactions can acquire method-group-specific locks because there
 will be no other active nor serial transaction.
 
 @end itemize
 
-There is no single rule for per-method-set blocking because this depends on
+There is no single rule for per-method-group blocking because this depends on
 when a TM method might acquire locks. If no active transaction can upgrade to
-being a serial transaction after it has acquired per-method-set locks (e.g.,
+being a serial transaction after it has acquired per-method-group locks (e.g.,
 when those locks are only acquired during an attempt to commit), then the TM
 method does not need to consider a potential deadlock due to serial mode.
 
 If there can be upgrades to serial mode after the acquisition of
-per-method-set locks, then TM methods need to avoid those deadlocks:
+per-method-group locks, then TM methods need to avoid those deadlocks:
 @itemize @bullet
 @item When upgrading to a serial transaction, after acquiring exclusive rights
 to the serial lock but before waiting for concurrent active transactions to
 finish (@pxref{serial-lock-impl,,Serial lock implementation} for details),
 we have to wake up all active transactions waiting on the upgrader's
-per-method-set locks.
-@item Active transactions blocking on per-method-set locks need to check the
+per-method-group locks.
+@item Active transactions blocking on per-method-group locks need to check the
 serial lock and abort if there is a pending serial transaction.
 @item Lost wake-ups have to be prevented (e.g., by changing a bit in each
-per-method-set lock before doing the wake-up, and only blocking on this lock
-using a futex if this bit is not set).
+per-method-group lock before doing the wake-up, and only blocking on this lock
+using a futex if this bit is not group).
 @end itemize
 
 @strong{TODO}: Can reuse serial lock for gl-*? And if we can, does it make
@@ -647,7 +688,7 @@  a sufficiently recent snapshot to not access the privatized data anymore. This
 happens after the privatizing transaction has stopped being an active
 transaction, so waiting for quiescence does not contribute to deadlocks.
 
-In method sets that need to ensure publication safety explicitly, active
+In method groups that need to ensure publication safety explicitly, active
 transactions maintain a flag or timestamp in the public/shared part of the
 transaction descriptor. Before blocking, privatizers need to let the other
 transactions know that they should wake up the privatizer.
@@ -699,9 +740,6 @@  or higher throughput for high-priority threads. Therefore, it probably makes
 not that much sense (except for eventual progress guarantees) to use
 priority inheritance until the TM has priority-aware contention management.
 
-@strong{TODO}: method sets, MS switch protocol and state (re)init after
-switch (e.g., clocks and counters),...
-
 
 @c ---------------------------------------------------------------------
 @c GNU General Public License
diff --git a/libitm/libitm_i.h b/libitm/libitm_i.h
index 5e4f5d9..ea89870 100644
--- a/libitm/libitm_i.h
+++ b/libitm/libitm_i.h
@@ -194,6 +194,8 @@  struct gtm_thread
 
   // Data used by retry.c for deciding what STM implementation should
   // be used for the next iteration of the transaction.
+  // Only restart_total is reset to zero when the transaction commits, the
+  // other counters are total values for all previously executed transactions.
   uint32_t restart_reason[NUM_RESTARTS];
   uint32_t restart_total;
 
@@ -215,6 +217,8 @@  struct gtm_thread
 
   // The head of the list of all threads' transactions.
   static gtm_thread *list_of_threads;
+  // The number of all registered threads.
+  static unsigned number_of_threads;
 
   // In alloc.cc
   void commit_allocations (bool, aa_tree<uintptr_t, gtm_alloc_action>*);
@@ -250,8 +254,12 @@  struct gtm_thread
   void drop_references_local (const void *, size_t);
 
   // In retry.cc
+  // Must be called outside of transactions (i.e., after rollback).
   void decide_retry_strategy (gtm_restart_reason);
   abi_dispatch* decide_begin_dispatch (uint32_t prop);
+  void number_of_threads_changed(unsigned previous, unsigned now);
+  // Must be called from serial mode. Does not call set_abi_disp().
+  void set_default_dispatch(abi_dispatch* disp);
 
   // In method-serial.cc
   void serialirr_mode ();
diff --git a/libitm/method-serial.cc b/libitm/method-serial.cc
index 6c803d3..4621345 100644
--- a/libitm/method-serial.cc
+++ b/libitm/method-serial.cc
@@ -35,15 +35,28 @@  using namespace GTM;
 
 namespace {
 
+// This group consists of the serial, serialirr, and serialirr_onwrite
+// methods, which all need no global state (except what is already provided
+// by the serial mode implementation).
+struct serial_mg : public method_group
+{
+  virtual void init() { }
+  virtual void fini() { }
+};
+
+static serial_mg o_serial_mg;
+
+
 class serialirr_dispatch : public abi_dispatch
 {
  public:
-  serialirr_dispatch() : abi_dispatch(false, true, true, false) { }
+  serialirr_dispatch() : abi_dispatch(false, true, true, false, &o_serial_mg)
+  { }
 
  protected:
   serialirr_dispatch(bool ro, bool wt, bool uninstrumented,
-      bool closed_nesting) :
-    abi_dispatch(ro, wt, uninstrumented, closed_nesting) { }
+      bool closed_nesting, method_group* mg) :
+    abi_dispatch(ro, wt, uninstrumented, closed_nesting, mg) { }
 
   // Transactional loads and stores simply access memory directly.
   // These methods are static to avoid indirect calls, and will be used by the
@@ -79,8 +92,6 @@  class serialirr_dispatch : public abi_dispatch
 
   virtual bool trycommit() { return true; }
   virtual void rollback(gtm_transaction_cp *cp) { abort(); }
-  virtual void reinit() { }
-  virtual void fini() { }
 
   virtual abi_dispatch* closed_nesting_alternative()
   {
@@ -134,13 +145,11 @@  public:
   // Local undo will handle this.
   // trydropreference() need not be changed either.
   virtual void rollback(gtm_transaction_cp *cp) { }
-  virtual void reinit() { }
-  virtual void fini() { }
 
   CREATE_DISPATCH_METHODS(virtual, )
   CREATE_DISPATCH_METHODS_MEM()
 
-  serial_dispatch() : abi_dispatch(false, true, false, true) { }
+  serial_dispatch() : abi_dispatch(false, true, false, true, &o_serial_mg) { }
 };
 
 
@@ -151,7 +160,7 @@  class serialirr_onwrite_dispatch : public serialirr_dispatch
 {
  public:
   serialirr_onwrite_dispatch() :
-    serialirr_dispatch(false, true, false, false) { }
+    serialirr_dispatch(false, true, false, false, &o_serial_mg) { }
 
  protected:
   static void pre_write()
@@ -238,17 +247,13 @@  GTM::gtm_thread::serialirr_mode ()
       // Given that we're already serial, the trycommit better work.
       bool ok = disp->trycommit ();
       assert (ok);
-      disp->fini ();
       need_restart = false;
     }
   else if (serial_lock.write_upgrade (this))
     {
       this->state |= STATE_SERIAL;
       if (disp->trycommit ())
-	{
-	  disp->fini ();
-	  need_restart = false;
-	}
+        need_restart = false;
     }
 
   if (need_restart)
diff --git a/libitm/retry.cc b/libitm/retry.cc
index 957da1e..630ca1a 100644
--- a/libitm/retry.cc
+++ b/libitm/retry.cc
@@ -22,8 +22,16 @@ 
    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.
+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)
 {
@@ -33,8 +41,10 @@  GTM::gtm_thread::decide_retry_strategy (gtm_restart_reason r)
   this->restart_total++;
 
   bool retry_irr = (r == RESTART_SERIAL_IRR);
-  bool retry_serial = (this->restart_total > 100 || retry_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;
 
@@ -45,10 +55,12 @@  GTM::gtm_thread::decide_retry_strategy (gtm_restart_reason r)
       // 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.
-      // FIXME this might be dangerous if we use serial mode to change TM
-      // meta data (e.g., reallocate the lock array). Likewise, for
-      // privatization, we must get rid of old references (that is, abort)
-      // or let privatizers know we're still there by not releasing the lock.
+      // 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;
@@ -56,20 +68,21 @@  GTM::gtm_thread::decide_retry_strategy (gtm_restart_reason r)
 	  serial_lock.write_lock ();
 	}
 
-      // ??? We can only retry with dispatch_serial when the transaction
-      // doesn't contain an abort.
+      // 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->fini ();
       disp = dispatch_serialirr ();
       set_abi_disp (disp);
     }
-  else
+  else if (retry_serial)
     {
       disp = dispatch_serial();
       set_abi_disp (disp);
@@ -79,18 +92,130 @@  GTM::gtm_thread::decide_retry_strategy (gtm_restart_reason r)
 
 // Decides which TM method should be used on the first attempt to run this
 // transaction.
-// serial_lock will not have been acquired if this is the outer-most
-// transaction. If the state is set to STATE_SERIAL, the caller will set the
-// dispatch.
 GTM::abi_dispatch*
 GTM::gtm_thread::decide_begin_dispatch (uint32_t prop)
 {
-  // ??? Probably want some environment variable to choose the default
-  // STM implementation once we have more than one implemented.
-  if (prop & pr_hasNoAbort)
-    return dispatch_serialirr_onwrite();
-  state = STATE_SERIAL;
-  if (prop & pr_hasNoAbort)
-    state |= STATE_IRREVOCABLE;
+  // 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
+    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());
+    }
+}