diff --git a/libitm/method-gl.cc b/libitm/method-gl.cc
index 17a2b9f..1dc700a 100644
--- a/libitm/method-gl.cc
+++ b/libitm/method-gl.cc
@@ -72,6 +72,12 @@ static gl_mg o_gl_mg;
 // validate that no other update transaction comitted before we acquired the
 // orec, so we have the most recent timestamp and no other transaction can
 // commit until we have committed).
+// However, we therefore cannot use this method for a serial transaction
+// (because shared_state needs to remain at ~0) and we have to be careful
+// when switching to serial mode (see the special handling in trycommit() and
+// rollback()).
+// ??? This sharing adds some complexity wrt. serial mode. Just use a separate
+// state variable?
 class gl_wt_dispatch : public abi_dispatch
 {
 protected:
@@ -202,10 +208,22 @@ public:
 
   virtual bool trycommit(gtm_word& priv_time)
   {
-    // Release the orec but do not reset shared_state, which will be modified
-    // by the serial lock right after our commit anyway.
     gtm_thread* tx = gtm_thr();
     gtm_word v = tx->shared_state;
+
+    // Special case: If shared_state is ~0, then we have acquired the
+    // serial lock (tx->state is not updated yet). In this case, the previous
+    // value isn't available anymore, so grab it from the global lock, which
+    // must have a meaningful value because no other transactions are active
+    // anymore. In particular, if it is locked, then we are an update
+    // transaction, which is all we care about for commit.
+    if (v == ~(typeof v)0)
+      v = o_gl_mg.orec;
+
+    // Release the orec but do not reset shared_state, which will be modified
+    // by the serial lock right after our commit anyway. Also, resetting
+    // shared state here would interfere with the serial lock's use of this
+    // location.
     if (gl_mg::is_locked(v))
       {
         // Release the global orec, increasing its version number / timestamp.
@@ -228,11 +246,20 @@ public:
     if (cp != 0)
       return;
 
+    gtm_thread *tx = gtm_thr();
+    gtm_word v = tx->shared_state;
+    // Special case: If shared_state is ~0, then we have acquired the
+    // serial lock (tx->state is not updated yet). In this case, the previous
+    // value isn't available anymore, so grab it from the global lock, which
+    // must have a meaningful value because no other transactions are active
+    // anymore. In particular, if it is locked, then we are an update
+    // transaction, which is all we care about for rollback.
+    if (v == ~(typeof v)0)
+      v = o_gl_mg.orec;
+
     // Release lock and increment version number to prevent dirty reads.
     // Also reset shared state here, so that begin_or_restart() can expect a
     // value that is correct wrt. privatization safety.
-    gtm_thread *tx = gtm_thr();
-    gtm_word v = tx->shared_state;
     if (gl_mg::is_locked(v))
       {
         // Release the global orec, increasing its version number / timestamp.
@@ -242,7 +269,11 @@ public:
         o_gl_mg.orec = v;
 
         // Also reset the timestamp published via shared_state.
-        tx->shared_state = v;
+        // Special case: Only do this if we are not a serial transaction
+        // because otherwise, we would interfere with the serial lock.
+        if (tx->shared_state != ~(typeof tx->shared_state)0)
+          tx->shared_state = v;
+
         // We need a store-load barrier after this store to prevent it
         // from becoming visible after later data loads because the
         // previous value of shared_state has been higher than the actual
