]> pd.if.org Git - nbds/blob - map/list.c
port to 32 bit x86 linux
[nbds] / map / list.c
1 /* 
2  * Written by Josh Dybnis and released to the public domain, as explained at
3  * http://creativecommons.org/licenses/publicdomain
4  *
5  * Harris-Michael lock-free list-based set
6  * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf
7  */
8
9 #include <stdio.h>
10 #include <string.h>
11
12 #include "common.h"
13 #include "list.h"
14 #include "mem.h"
15 #ifdef LIST_USE_HAZARD_POINTER
16 #include "hazard.h"
17 #else
18 #include "rcu.h"
19 #endif
20
21 typedef struct node {
22     map_key_t  key;
23     map_val_t  val;
24     markable_t next; // next node
25 } node_t;
26
27 struct ll_iter {
28     node_t *pred;
29 };
30
31 struct ll {
32     node_t *head;
33     const datatype_t *key_type;
34 };
35
36 // Marking the <next> field of a node logically removes it from the list
37 #define  MARK_NODE(x) TAG_VALUE((markable_t)(x), 0x1)
38 #define   HAS_MARK(x) (IS_TAGGED((x), 0x1) == 0x1)
39 #define   GET_NODE(x) ((node_t *)(x))
40 #define STRIP_MARK(x) ((node_t *)STRIP_TAG((x), 0x1))
41
42 static node_t *node_alloc (map_key_t key, map_val_t val) {
43     node_t *item = (node_t *)nbd_malloc(sizeof(node_t));
44     assert(!HAS_MARK((size_t)item));
45     item->key = key;
46     item->val = val;
47     return item;
48 }
49
50 list_t *ll_alloc (const datatype_t *key_type) {
51     list_t *ll = (list_t *)nbd_malloc(sizeof(list_t));
52     ll->key_type = key_type;
53     ll->head = node_alloc(0, 0);
54     ll->head->next = DOES_NOT_EXIST;
55     return ll;
56 }
57
58 void ll_free (list_t *ll) {
59     node_t *item = STRIP_MARK(ll->head->next);
60     while (item != NULL) {
61         node_t *next = STRIP_MARK(item->next);
62         if (ll->key_type != NULL) {
63             nbd_free((void *)item->key);
64         }
65         nbd_free(item);
66         item = next;
67     }
68 }
69
70 size_t ll_count (list_t *ll) {
71     size_t count = 0;
72     node_t *item = STRIP_MARK(ll->head->next);
73     while (item) {
74         if (!HAS_MARK(item->next)) {
75             count++;
76         }
77         item = STRIP_MARK(item->next);
78     }
79     return count;
80 }
81
82 #ifdef LIST_USE_HAZARD_POINTER
83 static void nbd_free_node (node_t *x) {
84     nbd_free((void *)x->key);
85     nbd_free(x);
86 }
87 #endif
88
89 static int find_pred (node_t **pred_ptr, node_t **item_ptr, list_t *ll, map_key_t key, int help_remove) {
90     node_t *pred = ll->head;
91     node_t *item = GET_NODE(pred->next);
92     TRACE("l2", "find_pred: searching for key %p in list (head is %p)", key, pred);
93 #ifdef LIST_USE_HAZARD_POINTER
94     haz_t *temp, *hp0 = haz_get_static(0), *hp1 = haz_get_static(1);
95 #endif
96
97     while (item != NULL) {
98 #ifdef LIST_USE_HAZARD_POINTER
99         haz_set(hp0, item);
100         if (STRIP_MARK(pred->next) != item)
101             return find_pred(pred_ptr, item_ptr, ll, key, help_remove); // retry
102 #endif
103         markable_t next = item->next;
104
105         // A mark means the node is logically removed but not physically unlinked yet.
106         while (EXPECT_FALSE(HAS_MARK(next))) {
107
108             // Skip over logically removed items.
109             if (!help_remove) {
110                 item = STRIP_MARK(item->next);
111                 if (EXPECT_FALSE(item == NULL))
112                     break;
113                 TRACE("l3", "find_pred: skipping marked item %p (next is %p)", item, next);
114                 next = item->next;
115                 continue;
116             }
117
118             // Unlink logically removed items.
119             TRACE("l3", "find_pred: unlinking marked item %p next is %p", item, next);
120
121             markable_t other = SYNC_CAS(&pred->next, item, STRIP_MARK(next));
122             if (other == (markable_t)item) {
123                 TRACE("l2", "find_pred: unlinked item %p from pred %p", item, pred);
124                 item = STRIP_MARK(next);
125                 next = (item != NULL) ? item->next : DOES_NOT_EXIST;
126                 TRACE("l3", "find_pred: now current item is %p next is %p", item, next);
127
128                 // The thread that completes the unlink should free the memory.
129 #ifdef LIST_USE_HAZARD_POINTER
130                 free_t free_ = (ll->key_type != NULL ? (free_t)nbd_free_node : nbd_free);
131                 haz_defer_free(GET_NODE(other), free_);
132 #else
133                 if (ll->key_type != NULL) {
134                     rcu_defer_free((void *)GET_NODE(other)->key);
135                 }
136                 rcu_defer_free(GET_NODE(other));
137 #endif
138             } else {
139                 TRACE("l2", "find_pred: lost a race to unlink item %p from pred %p", item, pred);
140                 TRACE("l2", "find_pred: pred's link changed to %p", other, 0);
141                 if (HAS_MARK(other))
142                     return find_pred(pred_ptr, item_ptr, ll, key, help_remove); // retry
143                 item = GET_NODE(other);
144                 next = (item != NULL) ? item->next : DOES_NOT_EXIST;
145             }
146         }
147
148         if (EXPECT_FALSE(item == NULL))
149             break;
150
151         TRACE("l3", "find_pred: visiting item %p (next is %p)", item, next);
152         TRACE("l4", "find_pred: key %p val %p", item->key, item->val);
153
154         int d;
155         if (EXPECT_TRUE(ll->key_type == NULL)) {
156             d = item->key - key;
157         } else {
158             d = ll->key_type->cmp((void *)item->key, (void *)key);
159         }
160
161         // If we reached the key (or passed where it should be), we found the right predesssor
162         if (d >= 0) {
163             if (pred_ptr != NULL) {
164                 *pred_ptr = pred;
165             }
166             if (item_ptr != NULL) {
167                 *item_ptr = item;
168             }
169             if (d == 0) {
170                 TRACE("l2", "find_pred: found matching item %p in list, pred is %p", item, pred);
171                 return TRUE;
172             } 
173             TRACE("l2", "find_pred: found proper place for key %p in list, pred is %p", key, pred);
174             return FALSE;
175         }
176
177         pred = item;
178 #ifdef LIST_USE_HAZARD_POINTER
179         temp = hp0; hp0 = hp1; hp1 = temp;
180 #endif
181         item = GET_NODE(next);
182     }
183
184     // <key> is not in <ll>.
185     if (pred_ptr != NULL) {
186         *pred_ptr = pred;
187     }
188     *item_ptr = NULL;
189     TRACE("l2", "find_pred: reached end of list. last item is %p", pred, 0);
190     return FALSE;
191 }
192
193 // Fast find. Do not help unlink partially removed nodes and do not return the found item's predecessor.
194 map_val_t ll_lookup (list_t *ll, map_key_t key) {
195     TRACE("l1", "ll_lookup: searching for key %p in list %p", key, ll);
196     node_t *item;
197     int found = find_pred(NULL, &item, ll, key, FALSE);
198
199     // If we found an <item> matching the key return its value.
200     if (found) {
201         map_val_t val = item->val;
202         if (val != DOES_NOT_EXIST) {
203             TRACE("l1", "ll_lookup: found item %p. val %p. returning item", item, item->val);
204             return val;
205         }
206     }
207
208     TRACE("l1", "ll_lookup: no item in the list matched the key", 0, 0);
209     return DOES_NOT_EXIST;
210 }
211
212 map_val_t ll_cas (list_t *ll, map_key_t key, map_val_t expectation, map_val_t new_val) {
213     TRACE("l1", "ll_cas: key %p list %p", key, ll);
214     TRACE("l1", "ll_cas: expectation %p new value %p", expectation, new_val);
215     ASSERT((int64_t)new_val > 0);
216
217     do {
218         node_t *pred, *old_item;
219         int found = find_pred(&pred, &old_item, ll, key, TRUE);
220         if (!found) {
221
222             // There was not an item in the list that matches the key. 
223             if (EXPECT_FALSE(expectation != CAS_EXPECT_DOES_NOT_EXIST && expectation != CAS_EXPECT_WHATEVER)) {
224                 TRACE("l1", "ll_cas: the expectation was not met, the list was not changed", 0, 0);
225                 return DOES_NOT_EXIST; // failure
226             }
227
228             // Create a new item and insert it into the list.
229             TRACE("l2", "ll_cas: attempting to insert item between %p and %p", pred, pred->next);
230             map_key_t new_key = ll->key_type == NULL ? key : (map_key_t)ll->key_type->clone((void *)key);
231             node_t *new_item = node_alloc(new_key, new_val);
232             markable_t next = new_item->next = (markable_t)old_item;
233             markable_t other = SYNC_CAS(&pred->next, next, new_item);
234             if (other == next) {
235                 TRACE("l1", "ll_cas: successfully inserted new item %p", new_item, 0);
236                 return DOES_NOT_EXIST; // success
237             }
238
239             // Lost a race. Failed to insert the new item into the list.
240             TRACE("l1", "ll_cas: lost a race. CAS failed. expected pred's link to be %p but found %p", next, other);
241             if (ll->key_type != NULL) {
242                 nbd_free((void *)new_key);
243             }
244             nbd_free(new_item);
245             continue; // retry
246         }
247
248         // Found an item in the list that matches the key.
249         map_val_t old_item_val = old_item->val;
250         do {
251             // If the item's value is DOES_NOT_EXIST it means another thread removed the node out from under us.
252             if (EXPECT_FALSE(old_item_val == DOES_NOT_EXIST)) {
253                 TRACE("l2", "ll_cas: lost a race, found an item but another thread removed it. retry", 0, 0);
254                 break; // retry
255             }
256
257             if (EXPECT_FALSE(expectation == CAS_EXPECT_DOES_NOT_EXIST)) {
258                 TRACE("l1", "ll_cas: found an item %p in the list that matched the key. the expectation was "
259                         "not met, the list was not changed", old_item, old_item_val);
260                 return old_item_val; // failure
261             }
262
263             // Use a CAS and not a SWAP. If the node is in the process of being removed and we used a SWAP, we could
264             // replace DOES_NOT_EXIST with our value. Then another thread that is updating the value could think it
265             // succeeded and return our value even though we indicated that the node has been removed. If the CAS 
266             // fails it means another thread either removed the node or updated its value.
267             map_val_t ret_val = SYNC_CAS(&old_item->val, old_item_val, new_val);
268             if (ret_val == old_item_val) {
269                 TRACE("l1", "ll_cas: the CAS succeeded. updated the value of the item", 0, 0);
270                 return ret_val; // success
271             }
272             TRACE("l2", "ll_cas: lost a race. the CAS failed. another thread changed the item's value", 0, 0);
273
274             old_item_val = ret_val;
275         } while (1);
276     } while (1);
277 }
278
279 map_val_t ll_remove (list_t *ll, map_key_t key) {
280     TRACE("l1", "ll_remove: removing item with key %p from list %p", key, ll);
281     node_t *pred;
282     node_t *item;
283     int found = find_pred(&pred, &item, ll, key, TRUE);
284     if (!found) {
285         TRACE("l1", "ll_remove: remove failed, an item with a matching key does not exist in the list", 0, 0);
286         return DOES_NOT_EXIST;
287     }
288
289     // Mark <item> removed. If multiple threads try to remove the same item only one of them should succeed.
290     markable_t next;
291     markable_t old_next = item->next;
292     do {
293         next     = old_next;
294         old_next = SYNC_CAS(&item->next, next, MARK_NODE(STRIP_MARK(next)));
295         if (HAS_MARK(old_next)) {
296             TRACE("l1", "ll_remove: lost a race -- %p is already marked for removal by another thread", item, 0);
297             return DOES_NOT_EXIST;
298         }
299     } while (next != old_next);
300     TRACE("l2", "ll_remove: logically removed item %p", item, 0);
301     ASSERT(HAS_MARK(((volatile node_t *)item)->next));
302
303     // Atomically swap out the item's value in case another thread is updating the item while we are 
304     // removing it. This establishes which operation occurs first logically, the update or the remove. 
305     map_val_t val = SYNC_SWAP(&item->val, DOES_NOT_EXIST); 
306     TRACE("l2", "ll_remove: replaced item's val %p with DOES_NOT_EXIT", val, 0);
307
308     // Unlink <item> from <ll>. If we lose a race to another thread just back off. It is safe to leave the
309     // item logically removed for a later call (or some other thread) to physically unlink. By marking the
310     // item earlier, we logically removed it. 
311     TRACE("l2", "ll_remove: unlink the item by linking its pred %p to its successor %p", pred, next);
312     markable_t other;
313     if ((other = SYNC_CAS(&pred->next, item, next)) != (markable_t)item) {
314         TRACE("l1", "ll_remove: unlink failed; pred's link changed from %p to %p", item, other);
315         return val;
316     } 
317
318     // The thread that completes the unlink should free the memory.
319 #ifdef LIST_USE_HAZARD_POINTER
320     free_t free_ = (ll->key_type != NULL ? (free_t)nbd_free_node : nbd_free);
321     haz_defer_free(GET_NODE(item), free_);
322 #else
323     if (ll->key_type != NULL) {
324         rcu_defer_free((void *)item->key);
325     }
326     rcu_defer_free(item);
327 #endif
328     TRACE("l1", "ll_remove: successfully unlinked item %p from the list", item, 0);
329     return val;
330 }
331
332 void ll_print (list_t *ll) {
333     markable_t next = ll->head->next;
334     int i = 0;
335     while (next != DOES_NOT_EXIST) {
336         node_t *item = STRIP_MARK(next);
337         if (item == NULL)
338             break;
339         printf("%s%p:0x%llx ", HAS_MARK(item->next) ? "*" : "", item, (uint64_t)item->key);
340         fflush(stdout);
341         if (i++ > 30) {
342             printf("...");
343             break;
344         }
345         next = item->next;
346     }
347     printf("\n");
348 }
349
350 ll_iter_t *ll_iter_begin (list_t *ll, map_key_t key) {
351     ll_iter_t *iter = (ll_iter_t *)nbd_malloc(sizeof(ll_iter_t));
352     if (key != DOES_NOT_EXIST) {
353         find_pred(&iter->pred, NULL, ll, key, FALSE);
354     } else {
355         iter->pred = ll->head;
356     }
357 #ifdef LIST_USE_HAZARD_POINTER
358     haz_register_dynamic((void **)&iter->pred);
359 #endif
360     return iter;
361 }
362
363 map_val_t ll_iter_next (ll_iter_t *iter, map_key_t *key_ptr) {
364     assert(iter);
365     if (iter->pred == NULL)
366         return DOES_NOT_EXIST;
367
368     // advance iterator to next item; skip items that have been removed
369     markable_t item;
370 #ifdef LIST_USE_HAZARD_POINTER 
371     haz_t *hp0 = haz_get_static(0);
372 #endif
373     do {
374 #ifndef LIST_USE_HAZARD_POINTER 
375         item = iter->pred->next;
376 #else //LIST_USE_HAZARD_POINTER 
377         do {
378             item = iter->pred->next;
379             haz_set(hp0, STRIP_MARK(item));
380         } while (item != ((volatile node_t *)iter->pred)->next);
381 #endif//LIST_USE_HAZARD_POINTER
382         iter->pred = STRIP_MARK(item);
383         if (iter->pred == NULL)
384             return DOES_NOT_EXIST;
385     } while (HAS_MARK(item));
386
387     if (key_ptr != NULL) {
388         *key_ptr = GET_NODE(item)->key;
389     }
390     return GET_NODE(item)->val;
391 }
392
393 void ll_iter_free (ll_iter_t *iter) {
394 #ifdef LIST_USE_HAZARD_POINTER
395     haz_unregister_dynamic((void **)&iter->pred);
396 #endif
397     nbd_free(iter);
398 }