tlds
Transactional Operations for Linked Data Structures
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
WriteSet.hpp
Go to the documentation of this file.
1 /**
2  * Copyright (C) 2011
3  * University of Rochester Department of Computer Science
4  * and
5  * Lehigh University Department of Computer Science and Engineering
6  *
7  * License: Modified BSD
8  * Please see the file LICENSE.RSTM for licensing information
9  */
10 
11 /**
12  * The RSTM backends that use redo logs all rely on this datastructure,
13  * which provides O(1) clear, insert, and lookup by maintaining a hashed
14  * index into a vector.
15  */
16 
17 #ifndef WRITESET_HPP__
18 #define WRITESET_HPP__
19 
20 #include <stm/config.h>
21 
22 #ifdef STM_CC_SUN
23 #include <string.h>
24 #include <stdlib.h>
25 #else
26 #include <cstdlib>
27 #include <cstring>
28 #endif
29 #include <cassert>
30 
31 namespace stm
32 {
33  /**
34  * The WriteSet implementation is heavily influenced by the configuration
35  * parameters, STM_WS_(WORD/BYTE)LOG, STM_PROTECT_STACK, and
36  * STM_ABORT_ON_THROW. This means that much of this file is ifdeffed
37  * accordingly.
38  */
39 
40  /**
41  * The log entry type when we're word-logging is pretty trivial, and just
42  * logs address/value pairs.
43  */
45  {
46  void** addr;
47  void* val;
48 
49  WordLoggingWriteSetEntry(void** paddr, void* pval)
50  : addr(paddr), val(pval)
51  { }
52 
53  /**
54  * Called when we are WAW an address, and we want to coalesce the
55  * write. Trivial for the word-based writeset, but complicated for the
56  * byte-based version.
57  */
58  void update(const WordLoggingWriteSetEntry& rhs) { val = rhs.val; }
59 
60  /**
61  * Check to see if the entry is completely contained within the given
62  * address range. We have some preconditions here w.r.t. alignment and
63  * size of the range. It has to be at least word aligned and word
64  * sized. This is currently only used with stack addresses, so we don't
65  * include asserts because we don't want to pay for them in the common
66  * case writeback loop.
67  */
68  bool filter(void** lower, void** upper)
69  {
70  return !(addr + 1 < lower || addr >= upper);
71  }
72 
73  /**
74  * Called during writeback to actually perform the logged write. This is
75  * trivial for the word-based set, but the byte-based set is more
76  * complicated.
77  */
78  void writeback() const { *addr = val; }
79 
80  /**
81  * Called during rollback if there is an exception object that we need to
82  * perform writes to. The address range is the range of addresses that
83  * we're looking for. If this log entry is contained in the range, we
84  * perform the writeback.
85  *
86  * NB: We're assuming a pretty well defined address range, in terms of
87  * size and alignment here, because the word-based writeset can only
88  * handle word-sized data.
89  */
90  void rollback(void** lower, void** upper)
91  {
92  assert((uint8_t*)upper - (uint8_t*)lower >= (int)sizeof(void*));
93  assert((uintptr_t)upper % sizeof(void*) == 0);
94  if (addr >= lower && (addr + 1) <= upper)
95  writeback();
96  }
97  };
98 
99  /**
100  * The log entry for byte logging is complicated by
101  *
102  * 1) the fact that we store a bitmask
103  * 2) that we need to treat the address/value/mask instance variables as
104  * both word types, and byte types.
105  *
106  * We do this with unions, which makes the use of these easier since it
107  * reduces the huge number of casts we perform otherwise.
108  *
109  * Union naming is important, since the outside world only directly deals
110  * with the word-sized fields.
111  */
113  {
114  union {
115  void** addr;
116  uint8_t* byte_addr;
117  };
118 
119  union {
120  void* val;
121  uint8_t byte_val[sizeof(void*)];
122  };
123 
124  union {
125  uintptr_t mask;
126  uint8_t byte_mask[sizeof(void*)];
127  };
128 
129  ByteLoggingWriteSetEntry(void** paddr, void* pval, uintptr_t pmask)
130  {
131  addr = paddr;
132  val = pval;
133  mask = pmask;
134  }
135 
136  /**
137  * Called when we are WAW an address, and we want to coalesce the
138  * write. Trivial for the word-based writeset, but complicated for the
139  * byte-based version.
140  *
141  * The new value is the bytes from the incoming log injected into the
142  * existing value, we mask out the bytes we want from the incoming word,
143  * mask the existing word, and union them.
144  */
146  {
147  // fastpath for full replacement
148  if (__builtin_expect(rhs.mask == (uintptr_t)~0x0, true)) {
149  val = rhs.val;
150  mask = rhs.mask;
151  }
152 
153  // bit twiddling for awkward intersection, avoids looping
154  uintptr_t new_val = (uintptr_t)rhs.val;
155  new_val &= rhs.mask;
156  new_val |= (uintptr_t)val & ~rhs.mask;
157  val = (void*)new_val;
158 
159  // the new mask is the union of the old mask and the new mask
160  mask |= rhs.mask;
161  }
162 
163  /**
164  * Check to see if the entry is completely contained within the given
165  * address range. We have some preconditions here w.r.t. alignment and
166  * size of the range. It has to be at least word aligned and word
167  * sized. This is currently only used with stack addresses, so we
168  * don't include asserts because we don't want to pay for them in the
169  * common case writeback loop.
170  *
171  * The byte-logging writeset can actually accommodate awkward
172  * intersections here using the mask, but we're not going to worry
173  * about that given the expected size/alignment of the range.
174  */
175  bool filter(void** lower, void** upper)
176  {
177  return !(addr + 1 < lower || addr >= upper);
178  }
179 
180  /**
181  * If we're byte-logging, we'll write out each byte individually when
182  * we're not writing a whole word. This turns all subword writes into
183  * byte writes, so we lose the original atomicity of (say) half-word
184  * writes in the original source. This isn't a correctness problem
185  * because of our transactional synchronization, but could be a
186  * performance problem if the system depends on sub-word writes for
187  * performance.
188  */
189  void writeback() const
190  {
191  if (__builtin_expect(mask == (uintptr_t)~0x0, true)) {
192  *addr = val;
193  return;
194  }
195 
196  // mask could be empty if we filtered out all of the bytes
197  if (mask == 0x0)
198  return;
199 
200  // write each byte if its mask is set
201  for (unsigned i = 0; i < sizeof(val); ++i)
202  if (byte_mask[i] == 0xff)
203  byte_addr[i] = byte_val[i];
204  }
205 
206  /**
207  * Called during the rollback loop in order to write out buffered
208  * writes to an exception object (represented by the address
209  * range). We don't assume anything about the alignment or size of the
210  * exception object.
211  */
212  void rollback(void** lower, void** upper)
213  {
214  // two simple cases first, no intersection or complete intersection.
215  if (addr + 1 < lower || addr >= upper)
216  return;
217 
218  if (addr >= lower && addr + 1 <= upper) {
219  writeback();
220  return;
221  }
222 
223  // odd intersection
224  for (unsigned i = 0; i < sizeof(void*); ++i) {
225  if ((byte_mask[i] == 0xff) &&
226  (byte_addr + i >= (uint8_t*)lower ||
227  byte_addr + i < (uint8_t*)upper))
228  byte_addr[i] = byte_val[i];
229  }
230  }
231  };
232 
233  /**
234  * Pick a write-set implementation, based on the configuration.
235  */
236 #if defined(STM_WS_WORDLOG)
237  typedef WordLoggingWriteSetEntry WriteSetEntry;
238 # define STM_WRITE_SET_ENTRY(addr, val, mask) addr, val
239 #elif defined(STM_WS_BYTELOG)
240  typedef ByteLoggingWriteSetEntry WriteSetEntry;
241 # define STM_WRITE_SET_ENTRY(addr, val, mask) addr, val, mask
242 #else
243 # error WriteSet logging granularity configuration error.
244 #endif
245 
246  /**
247  * The write set is an indexed array of WriteSetEntry elements. As with
248  * MiniVector, we make sure that certain expensive but rare functions are
249  * never inlined.
250  */
251  class WriteSet
252  {
253  /*** data type for the index */
254  struct index_t
255  {
256  size_t version;
257  void* address;
258  size_t index;
259 
260  index_t() : version(0), address(NULL), index(0) { }
261  };
262 
263  index_t* index; // hash entries
264  size_t shift; // for the hash function
265  size_t ilength; // max size of hash
266  size_t version; // version for fast clearing
267 
268  WriteSetEntry* list; // the array of actual data
269  size_t capacity; // max array size
270  size_t lsize; // elements in the array
271 
272 
273  /**
274  * hash function is straight from CLRS (that's where the magic
275  * constant comes from).
276  */
277  size_t hash(void* const key) const
278  {
279  static const unsigned long long s = 2654435769ull;
280  const unsigned long long r = ((unsigned long long)key) * s;
281  return (size_t)((r & 0xFFFFFFFF) >> shift);
282  }
283 
284  /**
285  * This doubles the size of the index. This *does not* do anything as
286  * far as actually doing memory allocation. Callers should delete[]
287  * the index table, increment the table size, and then reallocate it.
288  */
289  size_t doubleIndexLength();
290 
291  /**
292  * Supporting functions for resizing. Note that these are never
293  * inlined.
294  */
295  void rebuild();
296  void resize();
297  void reset_internal();
298 
299  public:
300 
301  WriteSet(const size_t initial_capacity);
302  ~WriteSet();
303 
304  /**
305  * Search function. The log is an in/out parameter, and the bool
306  * tells if the search succeeded. When we are byte-logging, the log's
307  * mask is updated to reflect the bytes in the returned value that are
308  * valid. In the case that we don't find anything, the mask is set to 0.
309  */
310  bool find(WriteSetEntry& log) const
311  {
312  size_t h = hash(log.addr);
313 
314  while (index[h].version == version) {
315  if (index[h].address != log.addr) {
316  // continue probing
317  h = (h + 1) % ilength;
318  continue;
319  }
320 #if defined(STM_WS_WORDLOG)
321  log.val = list[index[h].index].val;
322  return true;
323 #elif defined(STM_WS_BYTELOG)
324  // Need to intersect the mask to see if we really have a match. We
325  // may have a full intersection, in which case we can return the
326  // logged value. We can have no intersection, in which case we can
327  // return false. We can also have an awkward intersection, where
328  // we've written part of what we're trying to read. In that case,
329  // the "correct" thing to do is to read the word from memory, log
330  // it, and merge the returned value with the partially logged
331  // bytes.
332  WriteSetEntry& entry = list[index[h].index];
333  if (__builtin_expect((log.mask & entry.mask) == 0, false)) {
334  log.mask = 0;
335  return false;
336  }
337 
338  // The update to the mask transmits the information the caller
339  // needs to know in order to distinguish between a complete and a
340  // partial intersection.
341  log.val = entry.val;
342  log.mask = entry.mask;
343  return true;
344 #else
345 #error "Preprocessor configuration error."
346 #endif
347  }
348 
349 #if defined(STM_WS_BYTELOG)
350  log.mask = 0x0;
351 #endif
352  return false;
353  }
354 
355  /**
356  * Support for abort-on-throw and stack protection makes rollback
357  * tricky. We might need to write to an exception object, and/or
358  * filter writeback to protect the stack.
359  *
360  * NB: We use a macro to hide the fact that some rollback calls are
361  * really simple. This gets called by ~30 STM implementations
362  */
363 #if !defined (STM_ABORT_ON_THROW)
364  void rollback() { }
365 # define STM_ROLLBACK(log, stack, exception, len) log.rollback()
366 #else
367 # if !defined(STM_PROTECT_STACK)
368  void rollback(void**, size_t);
369 # define STM_ROLLBACK(log, stack, exception, len) log.rollback(exception, len)
370 # else
371  void rollback(void**, void**, size_t);
372 # define STM_ROLLBACK(log, stack, exception, len) log.rollback(stack, exception, len)
373 # endif
374 #endif
375 
376  /**
377  * Encapsulate writeback in this routine, so that we can avoid making
378  * modifications to lots of STMs when we need to change writeback for a
379  * particular compiler.
380  */
381 #if !defined(STM_PROTECT_STACK)
383  {
384 #else
385  TM_INLINE void writeback(void** upper_stack_bound)
386  {
387 #endif
388  for (iterator i = begin(), e = end(); i != e; ++i)
389  {
390 #ifdef STM_PROTECT_STACK
391  // See if this falls into the protected stack region, and avoid
392  // the writeback if that is the case. The filter call will update
393  // a byte-log's mask if there is an awkward intersection.
394  //
395  void* top_of_stack;
396  if (i->filter(&top_of_stack, upper_stack_bound))
397  continue;
398 #endif
399  i->writeback();
400  }
401  }
402 
403  /**
404  * Inserts an entry in the write set. Coalesces writes, which can
405  * appear as write reordering in a data-racy program.
406  */
407  void insert(const WriteSetEntry& log)
408  {
409  size_t h = hash(log.addr);
410 
411  // Find the slot that this address should hash to. If we find it,
412  // update the value. If we find an unused slot then it's a new
413  // insertion.
414  while (index[h].version == version) {
415  if (index[h].address != log.addr) {
416  h = (h + 1) % ilength;
417  continue; // continue probing at new h
418  }
419 
420  // there /is/ an existing entry for this word, we'll be updating
421  // it no matter what at this point
422  list[index[h].index].update(log);
423  return;
424  }
425 
426  // add the log to the list (guaranteed to have space)
427  list[lsize] = log;
428 
429  // update the index
430  index[h].address = log.addr;
431  index[h].version = version;
432  index[h].index = lsize;
433 
434  // update the end of the list
435  lsize += 1;
436 
437  // resize the list if needed
438  if (__builtin_expect(lsize == capacity, false))
439  resize();
440 
441  // if we reach our load-factor
442  // NB: load factor could be better handled rather than the magic
443  // constant 3 (used in constructor too).
444  if (__builtin_expect((lsize * 3) >= ilength, false))
445  rebuild();
446  }
447 
448  /*** size() lets us know if the transaction is read-only */
449  size_t size() const { return lsize; }
450 
451  /**
452  * We use the version number to reset in O(1) time in the common case
453  */
454  void reset()
455  {
456  lsize = 0;
457  version += 1;
458 
459  // check overflow
460  if (version != 0)
461  return;
462  reset_internal();
463  }
464 
465  /*** Iterator interface: iterate over the list, not the index */
466  typedef WriteSetEntry* iterator;
467  iterator begin() const { return list; }
468  iterator end() const { return list + lsize; }
469  };
470 }
471 
472 #endif // WRITESET_HPP__
void * val
Definition: WriteSet.hpp:120
void * address
Definition: WriteSet.hpp:257
~WriteSet()
Definition: types.cpp:92
uint8_t byte_val[sizeof(void *)]
Definition: WriteSet.hpp:121
void * val
Definition: WriteSet.hpp:47
void writeback() const
Definition: WriteSet.hpp:189
Definition: stm_fraser.c:61
WordLoggingWriteSetEntry(void **paddr, void *pval)
Definition: WriteSet.hpp:49
index_t()
Definition: WriteSet.hpp:260
void update(const WordLoggingWriteSetEntry &rhs)
Definition: WriteSet.hpp:58
void reset_internal()
Definition: types.cpp:132
uint8_t * byte_addr
Definition: WriteSet.hpp:116
void rebuild()
Definition: types.cpp:99
ByteLoggingWriteSetEntry(void **paddr, void *pval, uintptr_t pmask)
Definition: WriteSet.hpp:129
void resize()
Definition: types.cpp:122
void insert(const WriteSetEntry &log)
Definition: WriteSet.hpp:407
size_t version
Definition: WriteSet.hpp:256
WriteSetEntry * list
Definition: WriteSet.hpp:268
size_t version
Definition: WriteSet.hpp:266
uintptr_t mask
Definition: WriteSet.hpp:125
Definition: WriteSet.hpp:44
bool filter(void **lower, void **upper)
Definition: WriteSet.hpp:175
Definition: WriteSet.hpp:112
void ** addr
Definition: WriteSet.hpp:115
void rollback(void **lower, void **upper)
Definition: WriteSet.hpp:90
#define TM_INLINE
Definition: platform.hpp:77
Definition: WriteSet.hpp:251
bool filter(void **lower, void **upper)
Definition: WriteSet.hpp:68
size_t ilength
Definition: WriteSet.hpp:265
size_t capacity
Definition: WriteSet.hpp:269
void update(const ByteLoggingWriteSetEntry &rhs)
Definition: WriteSet.hpp:145
size_t shift
Definition: WriteSet.hpp:264
void ** addr
Definition: WriteSet.hpp:46
size_t hash(void *const key) const
Definition: WriteSet.hpp:277
index_t * index
Definition: WriteSet.hpp:263
void rollback()
Definition: WriteSet.hpp:364
void reset()
Definition: WriteSet.hpp:454
Definition: list.h:93
bool find(WriteSetEntry &log) const
Definition: WriteSet.hpp:310
void writeback() const
Definition: WriteSet.hpp:78
WriteSet(const size_t initial_capacity)
Definition: types.cpp:79
WriteSetEntry * iterator
Definition: WriteSet.hpp:466
size_t doubleIndexLength()
Definition: types.cpp:69
TM_INLINE void writeback()
Definition: WriteSet.hpp:382
Definition: WriteSet.hpp:254
uint8_t byte_mask[sizeof(void *)]
Definition: WriteSet.hpp:126
size_t lsize
Definition: WriteSet.hpp:270
iterator begin() const
Definition: WriteSet.hpp:467
iterator end() const
Definition: WriteSet.hpp:468
size_t size() const
Definition: WriteSet.hpp:449
size_t index
Definition: WriteSet.hpp:258
void rollback(void **lower, void **upper)
Definition: WriteSet.hpp:212