Intel(R) Threading Building Blocks Doxygen Documentation  version 4.2.3
x86_rtm_rw_mutex.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 2005-2019 Intel Corporation
3 
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 
16 
17 
18 
19 */
20 
21 #include "tbb/tbb_config.h"
22 #if __TBB_TSX_AVAILABLE
23 #include "tbb/spin_rw_mutex.h"
24 #include "tbb/tbb_machine.h"
25 #include "itt_notify.h"
26 #include "governor.h"
27 #include "tbb/atomic.h"
28 
29 // __TBB_RW_MUTEX_DELAY_TEST shifts the point where flags aborting speculation are
30 // added to the read-set of the operation. If 1, will add the test just before
31 // the transaction is ended; this technique is called lazy subscription.
32 // CAUTION: due to proven issues of lazy subscription, use of __TBB_RW_MUTEX_DELAY_TEST is discouraged!
33 #ifndef __TBB_RW_MUTEX_DELAY_TEST
34  #define __TBB_RW_MUTEX_DELAY_TEST 0
35 #endif
36 
37 #if defined(_MSC_VER) && defined(_Wp64)
38  // Workaround for overzealous compiler warnings in /Wp64 mode
39  #pragma warning (disable: 4244)
40 #endif
41 
42 namespace tbb {
43 
44 namespace interface8 {
45 namespace internal {
46 
47 // abort code for mutexes that detect a conflict with another thread.
48 // value is hexadecimal
49 enum {
50  speculation_transaction_aborted = 0x01,
51  speculation_can_retry = 0x02,
52  speculation_memadd_conflict = 0x04,
53  speculation_buffer_overflow = 0x08,
54  speculation_breakpoint_hit = 0x10,
55  speculation_nested_abort = 0x20,
56  speculation_xabort_mask = 0xFF000000,
57  speculation_xabort_shift = 24,
58  speculation_retry = speculation_transaction_aborted
59  | speculation_can_retry
60  | speculation_memadd_conflict
61 };
62 
63 // maximum number of times to retry
64 // TODO: experiment on retry values.
65 static const int retry_threshold_read = 10;
66 static const int retry_threshold_write = 10;
67 
69 void x86_rtm_rw_mutex::internal_release(x86_rtm_rw_mutex::scoped_lock& s) {
70  switch(s.transaction_state) {
71  case RTM_transacting_writer:
72  case RTM_transacting_reader:
73  {
74  __TBB_ASSERT(__TBB_machine_is_in_transaction(), "transaction_state && not speculating");
75 #if __TBB_RW_MUTEX_DELAY_TEST
76  if(s.transaction_state == RTM_transacting_reader) {
78  } else {
80  }
81 #endif
83  s.my_scoped_lock.mutex = NULL;
84  }
85  break;
86  case RTM_real_reader:
87  __TBB_ASSERT(!this->w_flag, "w_flag set but read lock acquired");
88  s.my_scoped_lock.release();
89  break;
90  case RTM_real_writer:
91  __TBB_ASSERT(this->w_flag, "w_flag unset but write lock acquired");
92  this->w_flag = false;
93  s.my_scoped_lock.release();
94  break;
95  case RTM_not_in_mutex:
96  __TBB_ASSERT(false, "RTM_not_in_mutex, but in release");
97  break;
98  default:
99  __TBB_ASSERT(false, "invalid transaction_state");
100  }
101  s.transaction_state = RTM_not_in_mutex;
102 }
103 
105 void x86_rtm_rw_mutex::internal_acquire_writer(x86_rtm_rw_mutex::scoped_lock& s, bool only_speculate)
106 {
107  __TBB_ASSERT(s.transaction_state == RTM_not_in_mutex, "scoped_lock already in transaction");
109  int num_retries = 0;
110  unsigned int abort_code;
111  do {
113  if(this->state) {
114  if(only_speculate) return;
115  do {
116  backoff.pause(); // test the spin_rw_mutex (real readers or writers)
117  } while(this->state);
118  }
119  // _xbegin returns -1 on success or the abort code, so capture it
120  if(( abort_code = __TBB_machine_begin_transaction()) == ~(unsigned int)(0) )
121  {
122  // started speculation
123 #if !__TBB_RW_MUTEX_DELAY_TEST
124  if(this->state) { // add spin_rw_mutex to read-set.
125  // reader or writer grabbed the lock, so abort.
127  }
128 #endif
129  s.transaction_state = RTM_transacting_writer;
130  // Don not wrap the following assignment to a function,
131  // because it can abort the transaction in debug. Need mutex for release().
132  s.my_scoped_lock.mutex = this;
133  return; // successfully started speculation
134  }
135  ++num_retries;
136  } while( (abort_code & speculation_retry) != 0 && (num_retries < retry_threshold_write) );
137  }
138 
139  if(only_speculate) return; // should apply a real try_lock...
140  s.my_scoped_lock.acquire(*this, true); // kill transactional writers
141  __TBB_ASSERT(!w_flag, "After acquire for write, w_flag already true");
142  w_flag = true; // kill transactional readers
143  s.transaction_state = RTM_real_writer;
144  return;
145 }
146 
148 // only_speculate : true if we are doing a try_acquire. If true and we fail to speculate, don't
149 // really acquire the lock, return and do a try_acquire on the contained spin_rw_mutex. If
150 // the lock is already held by a writer, just return.
151 void x86_rtm_rw_mutex::internal_acquire_reader(x86_rtm_rw_mutex::scoped_lock& s, bool only_speculate) {
152  __TBB_ASSERT(s.transaction_state == RTM_not_in_mutex, "scoped_lock already in transaction");
154  int num_retries = 0;
155  unsigned int abort_code;
156  do {
158  // if in try_acquire, and lock is held as writer, don't attempt to speculate.
159  if(w_flag) {
160  if(only_speculate) return;
161  do {
162  backoff.pause(); // test the spin_rw_mutex (real readers or writers)
163  } while(w_flag);
164  }
165  // _xbegin returns -1 on success or the abort code, so capture it
166  if((abort_code = __TBB_machine_begin_transaction()) == ~(unsigned int)(0) )
167  {
168  // started speculation
169 #if !__TBB_RW_MUTEX_DELAY_TEST
170  if(w_flag) { // add w_flag to read-set.
171  __TBB_machine_transaction_conflict_abort(); // writer grabbed the lock, so abort.
172  }
173 #endif
174  s.transaction_state = RTM_transacting_reader;
175  // Don not wrap the following assignment to a function,
176  // because it can abort the transaction in debug. Need mutex for release().
177  s.my_scoped_lock.mutex = this;
178  return; // successfully started speculation
179  }
180  // fallback path
181  // retry only if there is any hope of getting into a transaction soon
182  // Retry in the following cases (from Section 8.3.5 of Intel(R)
183  // Architecture Instruction Set Extensions Programming Reference):
184  // 1. abort caused by XABORT instruction (bit 0 of EAX register is set)
185  // 2. the transaction may succeed on a retry (bit 1 of EAX register is set)
186  // 3. if another logical processor conflicted with a memory address
187  // that was part of the transaction that aborted (bit 2 of EAX register is set)
188  // That is, retry if (abort_code & 0x7) is non-zero
189  ++num_retries;
190  } while( (abort_code & speculation_retry) != 0 && (num_retries < retry_threshold_read) );
191  }
192 
193  if(only_speculate) return;
194  s.my_scoped_lock.acquire( *this, false );
195  s.transaction_state = RTM_real_reader;
196 }
197 
199 
200 bool x86_rtm_rw_mutex::internal_upgrade(x86_rtm_rw_mutex::scoped_lock& s)
201 {
202  switch(s.transaction_state) {
203  case RTM_real_reader: {
204  s.transaction_state = RTM_real_writer;
205  bool no_release = s.my_scoped_lock.upgrade_to_writer();
206  __TBB_ASSERT(!w_flag, "After upgrade_to_writer, w_flag already true");
207  w_flag = true;
208  return no_release;
209  }
210  case RTM_transacting_reader:
211 #if !__TBB_RW_MUTEX_DELAY_TEST
212  if(this->state) { // add spin_rw_mutex to read-set.
213  // Real reader or writer holds the lock; so commit the read and re-acquire for write.
214  internal_release(s);
215  internal_acquire_writer(s);
216  return false;
217  } else
218 #endif
219  {
220  s.transaction_state = RTM_transacting_writer;
221  return true;
222  }
223  default:
224  __TBB_ASSERT(false, "Invalid state for upgrade");
225  return false;
226  }
227 }
228 
230 bool x86_rtm_rw_mutex::internal_downgrade(x86_rtm_rw_mutex::scoped_lock& s) {
231  switch(s.transaction_state) {
232  case RTM_real_writer:
233  s.transaction_state = RTM_real_reader;
234  __TBB_ASSERT(w_flag, "Before downgrade_to_reader w_flag not true");
235  w_flag = false;
236  return s.my_scoped_lock.downgrade_to_reader();
237  case RTM_transacting_writer:
238 #if __TBB_RW_MUTEX_DELAY_TEST
239  if(this->state) { // a reader or writer has acquired mutex for real.
241  }
242 #endif
243  s.transaction_state = RTM_transacting_reader;
244  return true;
245  default:
246  __TBB_ASSERT(false, "Invalid state for downgrade");
247  return false;
248  }
249 }
250 
252 // There may be reader(s) which acquired the spin_rw_mutex, as well as possibly
253 // transactional reader(s). If this is the case, the acquire will fail, and assigning
254 // w_flag will kill the transactors. So we only assign w_flag if we have successfully
255 // acquired the lock.
256 bool x86_rtm_rw_mutex::internal_try_acquire_writer(x86_rtm_rw_mutex::scoped_lock& s)
257 {
258  internal_acquire_writer(s, /*only_speculate=*/true);
259  if(s.transaction_state == RTM_transacting_writer) {
260  return true;
261  }
262  __TBB_ASSERT(s.transaction_state == RTM_not_in_mutex, "Trying to acquire writer which is already allocated");
263  // transacting write acquire failed. try_acquire the real mutex
264  bool result = s.my_scoped_lock.try_acquire(*this, true);
265  if(result) {
266  // only shoot down readers if we're not transacting ourselves
267  __TBB_ASSERT(!w_flag, "After try_acquire_writer, w_flag already true");
268  w_flag = true;
269  s.transaction_state = RTM_real_writer;
270  }
271  return result;
272 }
273 
274 void x86_rtm_rw_mutex::internal_construct() {
275  ITT_SYNC_CREATE(this, _T("tbb::x86_rtm_rw_mutex"), _T(""));
276 }
277 
278 } // namespace internal
279 } // namespace interface8
280 } // namespace tbb
281 
282 #endif /* __TBB_TSX_AVAILABLE */
__int8 __TBB_EXPORTED_FUNC __TBB_machine_is_in_transaction()
static bool speculation_enabled()
Definition: governor.h:155
#define __TBB_ASSERT(predicate, comment)
No-op version of __TBB_ASSERT.
Definition: tbb_stddef.h:169
#define ITT_SYNC_CREATE(obj, type, name)
Definition: itt_notify.h:123
void pause()
Pause for a while.
Definition: tbb_machine.h:364
void __TBB_EXPORTED_FUNC __TBB_machine_end_transaction()
The graph class.
#define _T(string_literal)
Standard Windows style macro to markup the string literals.
Definition: itt_notify.h:66
Class that implements exponential backoff.
Definition: tbb_machine.h:349
void const char const char int ITT_FORMAT __itt_group_sync s
unsigned __int32 __TBB_EXPORTED_FUNC __TBB_machine_begin_transaction()
void __TBB_EXPORTED_FUNC __TBB_machine_transaction_conflict_abort()

Copyright © 2005-2019 Intel Corporation. All Rights Reserved.

Intel, Pentium, Intel Xeon, Itanium, Intel XScale and VTune are registered trademarks or trademarks of Intel Corporation or its subsidiaries in the United States and other countries.

* Other names and brands may be claimed as the property of others.