source: libcfa/src/collections/list2.hfa@ 8eb85de

stuck-waitfor-destruct
Last change on this file since 8eb85de was 9d3dc40, checked in by Michael Brooks <mlbrooks@…>, 4 weeks ago

Various changes motivated by improving CFA score on len-1 queues.

No such CFA score improvement achieved. Each change helped only on stripped-down, "try to isolate an important factor" tests. Generally, the changes are benign refactorings. (Results substantiating "don't hurt" are forthcoming.)

Libcfa changes are

  • move a read action from between the memory breaks to before them
  • make the memory breaks conditionally excluded (default included, as before)

Harness changes are

  • add width, a compiled-in number of lists to use in round-robin order; defaults to 1, which is what was happening all along
  • make the analysis scripts tolerate (so far, ignore) the width column
  • rename CLI arg NumNodes to Length (now NumNodes is Length * Width)
  • factor core testing loops into helper function runtest
  • switch to signal-based termination (and add uC++ work-around)
  • put "iterator threading" into ITERS_SAVE, joining preexisting "save iter into elem's self ref"; make iterator threading conditional on iterators-active
  • disable insertion loop counter and obs_*-variable declarations (and thus writes) when observation disabled
  • generalize observation to work on multiple lists
  • make observation and assertion-check-enabled mode work on stripped CFA list implementations like tagging-disabled
  • through this observation, ensure correctness of multi-list versions
  • Property mode set to 100644
File size: 26.7 KB
Line 
1// A revision of <collections/list.hfa>.
2// Supports headless (and headed) lists.
3// It's basically all good, and should become the official libcfa one, except:
4// - perf tests compare this one ("thesis's work") to libcfa ("old, before supporting headless")
5// - in libcfa, some uses of list.hfa make assumptions about list's private representation, which need fixing
6
7
8
9
10//
11// Cforall Version 1.0.0 Copyright (C) 2020 University of Waterloo
12//
13// The contents of this file are covered under the licence agreement in the
14// file "LICENCE" distributed with Cforall.
15//
16// list -- lets a user-defined stuct form intrusive linked lists
17//
18// Author : Michael Brooks
19// Created On : Wed Apr 22 18:00:00 2020
20// Last Modified By : Peter A. Buhr
21// Last Modified On : Thu Feb 2 11:32:26 2023
22// Update Count : 2
23//
24
25#pragma once
26
27#include <assert.h>
28
29forall( Decorator &, T & )
30struct tytagref {
31 inline T &;
32};
33
34forall( tOuter &, tMid &, tInner & )
35trait embedded {
36 tytagref( tMid, tInner ) ?`inner( tOuter & );
37};
38
39// embedded is reflexive, with no info (void) as the type tag
40forall (T &)
41static inline tytagref(void, T) ?`inner ( T & this ) { tytagref( void, T ) ret = {this}; return ret; }
42
43
44//
45// P9_EMBEDDED: Use on every case of plan-9 inheritance, to make "implements embedded" be a closure of plan-9 inheritance.
46//
47// struct foo {
48// int a, b, c;
49// inline (bar);
50// };
51// P9_EMBEDDED( foo, bar )
52//
53
54// usual version, for structs that are top-level declarations
55#define P9_EMBEDDED( derived, immedBase ) P9_EMBEDDED_DECL_( derived, immedBase, static ) P9_EMBEDDED_BDY_( immedBase )
56
57// special version, for structs that are declared in functions
58#define P9_EMBEDDED_INFUNC( derived, immedBase ) P9_EMBEDDED_DECL_( derived, immedBase, ) P9_EMBEDDED_BDY_( immedBase )
59
60// forward declarations of both the above; generally not needed
61// may help you control where the P9_EMBEEDED cruft goes, in case "right after the stuct" isn't where you want it
62#define P9_EMBEDDED_FWD( derived, immedBase ) P9_EMBEDDED_DECL_( derived, immedBase, static ) ;
63#define P9_EMBEDDED_FWD_INFUNC( derived, immedBase ) auto P9_EMBEDDED_DECL_( derived, immedBase, ) ;
64
65// private helpers
66#define P9_EMBEDDED_DECL_( derived, immedBase, STORAGE ) \
67 forall( Tbase &, TdiscardPath & | { tytagref( TdiscardPath, Tbase ) ?`inner( immedBase & ); } ) \
68 STORAGE inline tytagref(immedBase, Tbase) ?`inner( derived & this )
69
70#define P9_EMBEDDED_BDY_( immedBase ) { \
71 immedBase & ib = this; \
72 Tbase & b = ib`inner; \
73 tytagref(immedBase, Tbase) result = { b }; \
74 return result; \
75 }
76
77#define EMBEDDED_VIA( OUTER, MID, INNER ) \
78 (struct { tytagref(MID, INNER) ( * ?`inner ) ( OUTER & ); }){ ?`inner }
79
80#define DLINK_VIA( TE, TLINK ) \
81 EMBEDDED_VIA( TE, TLINK, dlink(TE) )
82
83// The origin is the position encountered at the start of iteration,
84// signifying, "need to advance to the first element," and at the end
85// of iteration, signifying, "no more elements." Normal comsumption of
86// an iterator runs advance as the first step, and uses the return
87// of advance as a guard, before dereferencing the iterator. So
88// normal consumption of an iterator does not dereference an iterator
89// in origin position. The value of a pointer (underlying a refence)
90// that is exposed publicly as an iteraor, and also a pointer stored
91// internally in a link field, is tagged, to indicate "is the origin"
92// (internally, is the list-head sentinel node), or untagged, to indicate
93// "is a regular node." Intent is to help a user who dereferences an
94// iterator in origin position (which would be an API-use error on their
95// part), by failing fast.
96
97#ifdef __EXPERIMENTAL_DISABLE_OTAG__ // Perf experimention alt mode
98
99 // With origin tagging disabled, iteration never reports "no more elements."
100 // In this mode, the list API is buggy.
101 // This mode is used to quantify the cost of the normal tagging scheme.
102
103 #define ORIGIN_TAG_ENABL(p) (p)
104 #define ORIGIN_TAG_CLEAR(p) (p)
105 #define ORIGIN_TAG_QUERY(p) 0
106 #define ORIGIN_TAG_ASGN(p, v) (p)
107 #define ORIGIN_TAG_EITHER(p, v) (p)
108 #define ORIGIN_TAG_NEQ(v1, v2) 0
109
110 #define TAGSONLY(...)
111 #define NOTAGS(...) __VA_ARGS__
112
113#else // Normal
114
115 #if defined( __x86_64 )
116 // Preferred case: tag in the most-significant bit. Dereference
117 // has been shown to segfault consistently. Maintenance should
118 // list more architectures as "ok" here, to let them use the
119 // preferred case, when valid.
120 #define ORIGIN_TAG_BITNO ( 8 * sizeof( size_t ) - 1 )
121 #else
122 // Fallback case: tag in the least-significant bit. Dereference
123 // will often give an alignment error, but may not, e.g. if
124 // accessing a char-typed member. 32-bit x86 uses the most-
125 // significant bit for real room on the heap.
126 #define ORIGIN_TAG_BITNO 0
127 #endif
128
129 #define ORIGIN_TAG_MASK (((size_t)1) << ORIGIN_TAG_BITNO)
130
131 #define ORIGIN_TAG_ENABL(p) ((p) | ORIGIN_TAG_MASK)
132 #define ORIGIN_TAG_CLEAR(p) ((p) & ~ORIGIN_TAG_MASK)
133 #define ORIGIN_TAG_QUERY(p) ((p) & ORIGIN_TAG_MASK)
134
135 #define ORIGIN_TAG_ASGN(p, v) ( \
136 verify( ! ORIGIN_TAG_QUERY(p) && "p had no tagbit" ), \
137 ORIGIN_TAG_EITHER((p), (v)) \
138 )
139
140 #define ORIGIN_TAG_EITHER(p, v) ( \
141 verify( ! ORIGIN_TAG_CLEAR(v) && "v is a pure tagbit" ), \
142 ( (p) | (v) ) \
143 )
144
145 #define ORIGIN_TAG_NEQ(v1, v2) ( \
146 verify( ! ORIGIN_TAG_CLEAR(v1) && "v1 is a pure tagbit" ), \
147 verify( ! ORIGIN_TAG_CLEAR(v2) && "v2 is a pure tagbit" ), \
148 ( (v1) ^ (v2) ) \
149 )
150
151 #define TAGSONLY(...) __VA_ARGS__
152 #define NOTAGS(...)
153
154#endif
155
156
157
158
159
160
161#ifdef __EXPERIMENTAL_LOOSE_SINGLES__ // Perf experimention alt mode
162
163 // In loose-singles mode, the ability to answer an "is listed" query is disabled, as is "to insert an element,
164 // it must not be listed already" checking. The user must know separately whether an element is listed.
165 // Other than inserting it, any list-api action on an unlisted element is undefined. Notably, list iteration
166 // starting from an unlisted element is not defined to respond "no more elements," and may instead continue
167 // iterating from a formerly occupied list position. This mode matches LQ usage.
168
169 #define NOLOOSE(...)
170 #define LOOSEONLY(...) __VA_ARGS__
171
172#else // Normal
173
174 #define NOLOOSE(...) __VA_ARGS__
175 #define LOOSEONLY(...)
176
177#endif
178
179
180
181
182
183
184
185
186// struct workaround0_t {};
187
188forall( tE & ) {
189
190 struct dlink;
191
192 // do not use; presence of the field declaration unblocks ability to define dlink (#304)
193 struct __dlink_selfref_workaround_t {
194 dlink(tE) *ref_notSelfRef;
195 };
196
197 struct dlink {
198 dlink(tE) *next; // TODO: rename with $
199 dlink(tE) *prev;
200 };
201
202 static inline void ?{}( dlink(tE) & this ) {
203 NOLOOSE(
204 dlink(tE) * toSelf = & this;
205 size_t toSelfNum = (size_t) toSelf;
206 size_t toSelfNumTagged = ORIGIN_TAG_ENABL( toSelfNum );
207 dlink(tE) * toSelfPtrTagged = (dlink(tE) *) toSelfNumTagged;
208 toSelf = toSelfPtrTagged;
209 (this.next){ toSelf };
210 (this.prev){ toSelf };
211 )
212 }
213
214 // You can "copy" a dlink. But the result won't be linked.
215 // Lets you copy what you inline the dlink into.
216 static inline void ?{}( dlink(tE) & this, dlink(tE) ) {
217 this{};
218 }
219
220 forall( tLinks & = dlink(tE) | embedded(tE, tLinks, dlink(tE)) ) {
221 struct dlist {
222 inline dlink(tE);
223 };
224
225 static inline tE * $get_list_origin_addr( dlist(tE, tLinks) & lst ) {
226 dlink(tE) & link_from_null = ( * (tE *) 0p )`inner;
227 ptrdiff_t link_offset = (ptrdiff_t) & link_from_null;
228 size_t origin_addr = ((size_t) & lst) - link_offset;
229 size_t preResult = ORIGIN_TAG_ENABL( origin_addr );
230 return (tE *)preResult;
231 }
232
233 static inline void ?{}( dlist(tE, tLinks) & this ) {
234 NOLOOSE(
235 ( (dlink(tE) &) this ){};
236 )
237 LOOSEONLY(
238 dlink(tE) * listOrigin = (dlink(tE) *) $get_list_origin_addr( this );
239 dlink( tE ) & thisl = this;
240 (thisl.prev) = listOrigin;
241 (thisl.next) = listOrigin;
242 )
243 }
244 }
245}
246
247forall( tE & ) {
248#ifdef __EXPERIMENTAL_DISABLE_OTAG__ // Perf experimention alt mode
249 static inline size_t origin_tag_query_arith$( tE & raw ) {
250 return 0;
251 }
252 static inline tE & nullif$( tE & val, size_t arith_ctrl ) {
253 verify( arith_ctrl == 0 ); (void) arith_ctrl;
254 return val;
255 }
256#else // Normal
257 // like ORIGIN_TAG_QUERY, but return is arithmetic number 0 or 1 (rather than 0 or non-0)
258 static inline size_t origin_tag_query_arith$( tE & raw ) {
259 size_t ret = (((size_t) & raw) >> ORIGIN_TAG_BITNO) & 1;
260 verify( ORIGIN_TAG_QUERY( (size_t) & raw ) ? ret == 1 : ret == 0 );
261 return ret;
262 }
263 // Requires arith_ctrl being 0 or 1.
264 // When 0, passes val through; when 1, returns null reference.
265 // Importantly, implemented without jumps or tests.
266 static inline tE & nullif$( tE & val, size_t arith_ctrl ) {
267 verify( ! ORIGIN_TAG_QUERY( (size_t) & val ) );
268 verify( arith_ctrl == 0 || arith_ctrl == 1 );
269 size_t mask_ctrl = ~ - arith_ctrl;
270 verify( arith_ctrl == 0 && mask_ctrl == -1 || arith_ctrl == 1 && mask_ctrl ==0 );
271 tE & ret = * (tE*) ( ((size_t) & val) & mask_ctrl);
272 verify( arith_ctrl == 0 && &ret == &val || arith_ctrl == 1 && &ret == 0p );
273 return ret;
274 }
275#endif
276}
277
278// Compile-time memory (cmem) barrier
279// Prevents the optimizer from reordering instructions across it
280// Originally included for correctness, though a broken state is not known to be reproducible.
281// Found to have a critical impact on performance:
282// - in the positions given by default: generally optimal
283// - absent: sometimes much slower, depending on the test harness
284// - in positions (that my be influenced by a principle but) that are arbitrary wrt microarchitecture: typically, much slower
285#ifdef __EXPERIMENTAL_DISABLE_CMEM_BARRIER__
286// upon request, disable cmem barriers
287#define MAYBE_CMEM_BARRIER
288#else
289// by default, enable cmem barriers
290#define MAYBE_CMEM_BARRIER asm( "" : : : "memory" )
291#endif
292
293// Insert read (location)
294// One of the read operations that occurs during an insert operation was found to be performace-critical under certain harnesses.
295// Arguably, the position should not matter if cmem barriers are off. Treating the factors as independent allows for measuring this idea.
296#ifdef __EXPERIMENTAL_DELAY_INSERT_READ__
297// upon request: do the read late (between the cmem barriers); this location is where the read was originally found when this insert read first became a performance-perterbing hypothesis
298#define MAYBE_INSERT_READ_EARLY(...)
299#define MAYBE_INSERT_READ_LATE(...) __VA_ARGS__
300#else
301// by default: do the read early (before the first cmem barrier); better performance has been seen here
302#define MAYBE_INSERT_READ_EARLY(...) __VA_ARGS__
303#define MAYBE_INSERT_READ_LATE(...)
304#endif
305
306forall( tE &, tLinks & | embedded( tE, tLinks, dlink(tE) ) ) {
307
308 static inline void insert_after(tE & list_pos, tE &to_insert) {
309 size_t list_pos_tag = ORIGIN_TAG_QUERY((size_t) & list_pos); // a request to insert after the origin is fine
310 tE & list_pos_real = * (tE *) ORIGIN_TAG_CLEAR((size_t) & list_pos);
311 verify (&list_pos_real != 0p);
312
313 verify (&to_insert != 0p);
314 NOLOOSE(
315 verify(! ORIGIN_TAG_QUERY((size_t) & to_insert));
316 )
317 dlink(tE) & linkToInsert = to_insert`inner;
318 NOLOOSE(
319 TAGSONLY(
320 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.prev));
321 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.next));
322 )
323 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.prev) == (size_t)&linkToInsert);
324 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.next) == (size_t)&linkToInsert);
325 )
326 dlink(tE) & list_pos_links = list_pos_real`inner;
327 MAYBE_INSERT_READ_EARLY(
328 dlink(tE) & afterLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.next );
329 )
330 MAYBE_CMEM_BARRIER;
331 size_t list_pos_links_num = (size_t)(& list_pos_links);
332 size_t to_insert_prev_num = ORIGIN_TAG_ASGN(list_pos_links_num, list_pos_tag);
333 dlink(tE) * to_insert_prev = (dlink(tE)*)to_insert_prev_num;
334 linkToInsert.prev = to_insert_prev;
335 linkToInsert.next = list_pos_links.next;
336 MAYBE_INSERT_READ_LATE(
337 dlink(tE) & afterLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.next );
338 )
339 size_t afterLinks_prev_tag = ORIGIN_TAG_QUERY((size_t)afterLinks.prev);
340 size_t linkToInsert_num = (size_t)(& linkToInsert);
341 size_t afterLinks_prev_num = ORIGIN_TAG_ASGN(linkToInsert_num, ORIGIN_TAG_NEQ(list_pos_tag, afterLinks_prev_tag));
342 afterLinks.prev = (dlink(tE)*)(afterLinks_prev_num);
343 list_pos_links.next = &linkToInsert;
344 MAYBE_CMEM_BARRIER;
345 }
346
347 static inline void insert_before(tE & list_pos, tE &to_insert) {
348 size_t list_pos_tag = ORIGIN_TAG_QUERY((size_t) & list_pos); // a request to insert before the origin is fine
349 tE & list_pos_real = * (tE *) ORIGIN_TAG_CLEAR((size_t) & list_pos);
350 verify (&list_pos_real != 0p);
351
352 verify (&to_insert != 0p);
353 NOLOOSE(
354 verify(! ORIGIN_TAG_QUERY((size_t) & to_insert));
355 )
356 dlink(tE) & linkToInsert = to_insert`inner;
357 NOLOOSE(
358 TAGSONLY(
359 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.prev));
360 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.next));
361 )
362 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.prev) == (size_t)&linkToInsert);
363 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.next) == (size_t)&linkToInsert);
364 )
365 dlink(tE) & list_pos_links = list_pos_real`inner;
366 MAYBE_INSERT_READ_EARLY(
367 dlink(tE) & beforeLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.prev );
368 )
369 MAYBE_CMEM_BARRIER;
370 size_t list_pos_links_num = (size_t)(& list_pos_links);
371 size_t to_insert_next_num = ORIGIN_TAG_ASGN(list_pos_links_num, list_pos_tag);
372 dlink(tE) * to_insert_next = (dlink(tE)*)to_insert_next_num;
373 linkToInsert.next = to_insert_next;
374 linkToInsert.prev = list_pos_links.prev;
375 MAYBE_INSERT_READ_LATE(
376 dlink(tE) & beforeLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.prev );
377 )
378 size_t beforeLinks_next_tag = ORIGIN_TAG_QUERY((size_t)beforeLinks.next);
379 size_t linkToInsert_num = (size_t)(& linkToInsert);
380 size_t beforeLinks_next_num = ORIGIN_TAG_ASGN(linkToInsert_num, ORIGIN_TAG_NEQ(list_pos_tag, beforeLinks_next_tag));
381 beforeLinks.next = (dlink(tE)*)(beforeLinks_next_num);
382 list_pos_links.prev = &linkToInsert;
383 MAYBE_CMEM_BARRIER;
384 }
385
386 static inline tE & remove(tE & list_pos) {
387 verify( ! ORIGIN_TAG_QUERY((size_t) & list_pos) );
388 verify (&list_pos != 0p);
389
390 dlink(tE) & list_pos_links = list_pos`inner;
391 dlink(tE) & before_raw = * list_pos_links.prev;
392 dlink(tE) & before_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & before_raw );
393 size_t before_links_next_tag = ORIGIN_TAG_QUERY( (size_t) (before_links.next) );
394
395 dlink(tE) & after_raw = * list_pos_links.next;
396 dlink(tE) & after_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & after_raw );
397 size_t after_links_prev_tag = ORIGIN_TAG_QUERY( (size_t) (after_links.prev) );
398
399 size_t before_links_next_rslt = ORIGIN_TAG_EITHER( ((size_t) & after_raw), before_links_next_tag );
400 size_t after_links_prev_rslt = ORIGIN_TAG_EITHER( ((size_t) & before_raw), after_links_prev_tag );
401 before_links.next = (dlink(tE) *) before_links_next_rslt;
402 after_links.prev = (dlink(tE) *) after_links_prev_rslt;
403
404 NOLOOSE(
405 MAYBE_CMEM_BARRIER;
406 size_t list_pos_links_num = (size_t) &list_pos_links;
407 size_t list_pos_links_tagged_num = ORIGIN_TAG_ENABL( list_pos_links_num );
408 list_pos_links.next = list_pos_links.prev = (dlink(tE)*) list_pos_links_tagged_num;
409 MAYBE_CMEM_BARRIER;
410 )
411 return list_pos;
412 }
413
414 static inline tE & downcast$( tytagref( tLinks, dlink(tE) ) ref ) {
415 dlink(tE) & lnk = ref;
416 dlink(tE) & link_from_null = ( * (tE *) 0p )`inner;
417 ptrdiff_t link_offset = (ptrdiff_t) & link_from_null;
418 size_t elm_addr = ((size_t) & lnk) - link_offset;
419 return * (tE*) elm_addr;
420 }
421
422 static inline tE & first( dlist(tE, tLinks) & lst ) {
423 verify (&lst != 0p);
424 dlink(tE) * firstLnk = lst.next;
425 if (ORIGIN_TAG_QUERY((size_t)firstLnk)) return * 0p;
426 tytagref( tLinks, dlink(tE) ) firstLnkTagged = {*firstLnk};
427 return downcast$( firstLnkTagged );
428 }
429 static inline tE & last ( dlist(tE, tLinks) & lst ) {
430 verify (&lst != 0p);
431 dlink(tE) * lastLnk = lst.prev;
432 if (ORIGIN_TAG_QUERY((size_t)lastLnk)) return * 0p;
433 tytagref( tLinks, dlink(tE) ) lastLnkTagged = {*lastLnk};
434 return downcast$( lastLnkTagged );
435 }
436
437 static inline bool isEmpty( dlist(tE, tLinks) & lst ) {
438 verify (&lst != 0p);
439 if ( & first(lst) == 0p || & last(lst) == 0p ) {
440 verify( & last(lst) == 0p && & last(lst) == 0p );
441 return true;
442 }
443 return false;
444 }
445
446 static inline bool isListed( tE & e ) {
447 NOLOOSE(
448 verify (&e != 0p);
449 verify(! ORIGIN_TAG_QUERY( (size_t) & e ));
450 dlink(tE) & e_links = e`inner;
451 dlink(tE) * lprev = (dlink(tE)*) ORIGIN_TAG_CLEAR((size_t) e_links.prev);
452 dlink(tE) * lnext = (dlink(tE)*) ORIGIN_TAG_CLEAR((size_t) e_links.next);
453 return ( lprev != &e_links ) || ( lnext != &e_links );
454 )
455 LOOSEONLY(
456 verify(false && "isListed is undefined");
457 return true;
458 )
459 }
460
461 static inline tE & iter( dlist(tE, tLinks) & lst ) {
462 tE * origin = $get_list_origin_addr( lst );
463 return *origin;
464 }
465
466
467 // todo: resolve the pun:
468 // tag as in proxy (tytagref)
469 // tag as in bit manipulation on a funny pointer
470
471 static inline bool advance( tE && refx ) {
472 tE && ref_inner = refx;
473 tE & oldReferent = * (tE*) ORIGIN_TAG_CLEAR( (size_t) & ref_inner );
474 verify (& oldReferent != 0p);
475 dlink(tE) * tgt = oldReferent`inner.next;
476 size_t tgt_tags = ORIGIN_TAG_QUERY( (size_t)tgt);
477 dlink(tE) * nextl = (dlink(tE) *)ORIGIN_TAG_CLEAR((size_t)tgt);
478 tytagref( tLinks, dlink(tE) ) nextLnkTagged = { * nextl };
479 tE & nexte = downcast$( nextLnkTagged );
480 size_t next_te_num = (size_t) & nexte;
481 size_t new_ref_inner_num = ORIGIN_TAG_ASGN(next_te_num, tgt_tags);
482 tE * new_ref_inner = (tE *) new_ref_inner_num;
483 &ref_inner = new_ref_inner;
484 return ! tgt_tags;
485 }
486
487 static inline bool recede( tE && refx ) {
488 tE && ref_inner = refx;
489 tE & oldReferent = * (tE*) ORIGIN_TAG_CLEAR( (size_t) & ref_inner );
490 verify (& oldReferent != 0p);
491 dlink(tE) * tgt = oldReferent`inner.prev;
492 size_t tgt_tags = ORIGIN_TAG_QUERY( (size_t)tgt);
493 dlink(tE) * prevl = (dlink(tE) *)ORIGIN_TAG_CLEAR((size_t)tgt);
494 tytagref( tLinks, dlink(tE) ) prevLnkTagged = { * prevl };
495 tE & preve = downcast$( prevLnkTagged );
496 size_t prev_te_num = (size_t) & preve;
497 size_t new_ref_inner_num = ORIGIN_TAG_ASGN(prev_te_num, tgt_tags);
498 tE * new_ref_inner = (tE *) new_ref_inner_num;
499 &ref_inner = new_ref_inner;
500 return ! tgt_tags;
501 }
502
503 bool isFirst( tE & node ) {
504 // Probable bug copied from master
505 // should be `! recede(node)`
506 // correct: "is first iff cannot recede"
507 // used backward in test suite too, probably victim of a grep rename
508 return recede( node );
509 }
510
511 bool isLast( tE & node ) {
512 // ditto, vice versa
513 return advance( node );
514 }
515
516 static inline tE & next( tE & e ) {
517 if( advance(e) ) return e;
518 return * 0p;
519 }
520
521 static inline tE & prev( tE & e ) {
522 if( recede(e) ) return e;
523 return * 0p;
524 }
525
526 // Next 4 headed operations:
527 // Manual inline of the equivalent headless operation, manually simplified.
528 // Applies knowledge of tag pattern around head (unknown to optimizer) to reduce runtime tag operations.
529
530 static inline void insert_first( dlist(tE, tLinks) &lst, tE & e ) {
531 dlink(tE) & linkToInsert = e`inner;
532 NOLOOSE(
533 TAGSONLY(
534 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.prev));
535 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.next));
536 )
537 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.prev) == (size_t)&linkToInsert);
538 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.next) == (size_t)&linkToInsert);
539 )
540 dlink(tE) & list_pos_links = lst;
541 MAYBE_INSERT_READ_EARLY(
542 dlink(tE) & afterLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.next );
543 )
544 MAYBE_CMEM_BARRIER;
545 size_t list_pos_links_num = (size_t)(& list_pos_links);
546 size_t to_insert_prev_num = ORIGIN_TAG_ENABL(list_pos_links_num);
547 dlink(tE) * to_insert_prev = (dlink(tE)*)to_insert_prev_num;
548 linkToInsert.prev = to_insert_prev;
549 linkToInsert.next = list_pos_links.next;
550 MAYBE_INSERT_READ_LATE(
551 dlink(tE) & afterLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.next );
552 )
553 size_t linkToInsert_num = (size_t)(& linkToInsert);
554 size_t afterLinks_prev_num = linkToInsert_num;
555 afterLinks.prev = (dlink(tE)*)(afterLinks_prev_num);
556 list_pos_links.next = &linkToInsert;
557 MAYBE_CMEM_BARRIER;
558 }
559
560 static inline void insert_last( dlist(tE, tLinks) &lst, tE & e ) {
561 dlink(tE) & linkToInsert = e`inner;
562 NOLOOSE(
563 TAGSONLY(
564 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.next));
565 verify(ORIGIN_TAG_QUERY((size_t)linkToInsert.prev));
566 )
567 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.next) == (size_t)&linkToInsert);
568 verify(ORIGIN_TAG_CLEAR((size_t)linkToInsert.prev) == (size_t)&linkToInsert);
569 )
570 dlink(tE) & list_pos_links = lst;
571 MAYBE_INSERT_READ_EARLY(
572 dlink(tE) & beforeLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.prev );
573 )
574 MAYBE_CMEM_BARRIER;
575 size_t list_pos_links_num = (size_t)(& list_pos_links);
576 size_t to_insert_next_num = ORIGIN_TAG_ENABL(list_pos_links_num);
577 dlink(tE) * to_insert_next = (dlink(tE)*)to_insert_next_num;
578 linkToInsert.next = to_insert_next;
579 linkToInsert.prev = list_pos_links.prev;
580 MAYBE_INSERT_READ_LATE(
581 dlink(tE) & beforeLinks = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) list_pos_links.prev );
582 )
583 size_t linkToInsert_num = (size_t)(& linkToInsert);
584 size_t beforeLinks_next_num = linkToInsert_num;
585 beforeLinks.next = (dlink(tE)*)(beforeLinks_next_num);
586 list_pos_links.prev = &linkToInsert;
587 MAYBE_CMEM_BARRIER;
588 }
589
590 static inline tE & remove_first( dlist(tE, tLinks) &lst ) {
591 verify (&lst != 0p);
592 dlink(tE) & list_links = lst;
593 // call is valid on empty list; when so, list_links.next and after_links.prev have otags set
594
595 dlink(tE) & fst_raw = * list_links.next;
596 dlink(tE) & fst_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & fst_raw );
597 size_t fst_tagval = origin_tag_query_arith$( fst_raw );
598
599 dlink(tE) & after_raw = * fst_links.next;
600 dlink(tE) & after_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & after_raw );
601
602 size_t before_links_next_rslt = ((size_t) & after_raw);
603 size_t after_links_prev_rslt = ORIGIN_TAG_ENABL( (size_t) & list_links );
604 list_links.next = (dlink(tE) *) before_links_next_rslt;
605 after_links.prev = (dlink(tE) *) after_links_prev_rslt;
606
607 MAYBE_CMEM_BARRIER;
608 size_t list_pos_links_num = (size_t) &fst_links;
609 size_t list_pos_links_tagged_num = ORIGIN_TAG_ENABL( list_pos_links_num );
610 fst_links.next = fst_links.prev = (dlink(tE)*) list_pos_links_tagged_num;
611 MAYBE_CMEM_BARRIER;
612
613 tytagref( tLinks, dlink(tE) ) retExt = { fst_links };
614 return nullif$( downcast$( retExt ), fst_tagval );
615 }
616
617 static inline tE & remove_last( dlist(tE, tLinks) &lst ) {
618 verify (&lst != 0p);
619 dlink(tE) & list_links = lst;
620 // call is valid on empty list; when so, list_links.prev and before_links.next have otags set
621
622 dlink(tE) & last_raw = * list_links.prev;
623 dlink(tE) & last_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & last_raw );
624 size_t last_tagval = origin_tag_query_arith$( last_raw );
625
626 dlink(tE) & before_raw = * last_links.prev;
627 dlink(tE) & before_links = * (dlink(tE) *) ORIGIN_TAG_CLEAR( (size_t) & before_raw );
628
629 size_t after_links_prev_rslt = ((size_t) & before_raw);
630 size_t before_links_next_rslt = ORIGIN_TAG_ENABL( (size_t) & list_links );
631 list_links.prev = (dlink(tE) *) after_links_prev_rslt;
632 before_links.next = (dlink(tE) *) before_links_next_rslt;
633
634 MAYBE_CMEM_BARRIER;
635 size_t list_pos_links_num = (size_t) &last_links;
636 size_t list_pos_links_tagged_num = ORIGIN_TAG_ENABL( list_pos_links_num );
637 last_links.prev = last_links.next = (dlink(tE)*) list_pos_links_tagged_num;
638 MAYBE_CMEM_BARRIER;
639
640 tytagref( tLinks, dlink(tE) ) lpLnkTagged = { last_links };
641 return nullif$( downcast$( lpLnkTagged ), last_tagval );
642 }
643
644 static inline tE & try_pop_first( dlist(tE, tLinks) &lst ) {
645 tE & first_inlist = first(lst);
646 tE & first_item = first_inlist;
647 if (&first_item) remove(first_inlist); // TODO: should it use pop_front?
648 return first_item;
649 }
650
651 static inline tE & try_pop_last( dlist(tE, tLinks) &lst ) {
652 tE & last_inlist = last(lst);
653 tE & last_item = last_inlist;
654 if (&last_item) remove(last_inlist); // TODO: should it use pop_back?
655 return last_item;
656 }
657
658
659 #if !defined(NDEBUG) && (defined(__CFA_DEBUG__) || defined(__CFA_VERIFY__))
660 static bool $validate_fwd( dlist(tE, tLinks) & this ) {
661 tE & lagElem = *0p;
662
663 while ( tE & it = iter(this); advance(it) ) {
664 if (& lagElem == 0p && &it != & first(this) ) return false;
665 & lagElem = & it;
666 }
667
668 if (& lagElem != & last(this)) return false;
669
670 // TODO: verify that it is back at iter(this);
671 return true;
672 }
673 static bool $validate_rev( dlist(tE, tLinks) & this ) {
674 tE & lagElem = *0p;
675
676 while ( tE & it = iter(this); recede(it) ) {
677 if (& lagElem == 0p && &it != & last(this) ) return false;
678 & lagElem = & it;
679 }
680
681 if (& lagElem != & first(this)) return false;
682
683 // TODO: verify that it is back at iter(this);
684 return true;
685 }
686 static inline bool validate( dlist(tE, tLinks) & this ) {
687 bool reportsHavingFirst = ((& first(this)) == 0p);
688 bool reportsHavingLast = ((& last(this)) == 0p);
689 if ( reportsHavingFirst != reportsHavingLast ) return false;
690
691 return $validate_fwd(this) && $validate_rev(this);
692 }
693 #endif
694
695}
Note: See TracBrowser for help on using the repository browser.