diff --git a/main/lib/data/c_circular.c b/main/lib/data/c_circular.c index 3d25d884a..da3fcd23e 100644 --- a/main/lib/data/c_circular.c +++ b/main/lib/data/c_circular.c @@ -282,7 +282,7 @@ BEGIN_PROPERTY(Circular_Overwrite) END_PROPERTY -GB_DESC CCircularDesc[] = { +GB_DESC CCircular[] = { GB_DECLARE("Circular", sizeof(CCIRCULAR)), GB_METHOD("_new", NULL, Circular_new, "(Size)i[(Overwrite)b]"), diff --git a/main/lib/data/c_circular.h b/main/lib/data/c_circular.h index 85e4bda31..505e4796b 100644 --- a/main/lib/data/c_circular.h +++ b/main/lib/data/c_circular.h @@ -27,7 +27,7 @@ extern GB_INTERFACE GB; #ifndef __C_CIRCULAR_C -extern GB_DESC CCircularDesc[]; +extern GB_DESC CCircular[]; #endif #endif /* !__C_CIRCULAR_H */ diff --git a/main/lib/data/c_deque.c b/main/lib/data/c_deque.c index 24a7c04da..5fec7df3d 100644 --- a/main/lib/data/c_deque.c +++ b/main/lib/data/c_deque.c @@ -227,7 +227,7 @@ END_PROPERTY * Double-ended queue */ -GB_DESC CDequeDesc[] = { +GB_DESC CDeque[] = { GB_DECLARE("Deque", sizeof(CDEQUE)), GB_METHOD("_new", NULL, Deque_new, NULL), @@ -249,10 +249,10 @@ GB_DESC CDequeDesc[] = { }; /* - * FILO + * LIFO */ -GB_DESC CStackDesc[] = { +GB_DESC CStack[] = { GB_DECLARE("Stack", sizeof(CDEQUE)), GB_METHOD("_new", NULL, Deque_new, NULL), @@ -274,7 +274,7 @@ GB_DESC CStackDesc[] = { * FIFO */ -GB_DESC CQueueDesc[] = { +GB_DESC CQueue[] = { GB_DECLARE("Queue", sizeof(CDEQUE)), GB_METHOD("_new", NULL, Deque_new, NULL), @@ -329,7 +329,7 @@ END_METHOD * Priority FIFO */ -GB_DESC CPrioQueueDesc[] = { +GB_DESC CPrioQueue[] = { GB_DECLARE("PrioQueue", sizeof(CDEQUE)), GB_METHOD("_new", NULL, Deque_new, NULL), diff --git a/main/lib/data/c_deque.h b/main/lib/data/c_deque.h index 30ac5a7f2..34ca80801 100644 --- a/main/lib/data/c_deque.h +++ b/main/lib/data/c_deque.h @@ -27,11 +27,10 @@ extern GB_INTERFACE GB; #ifndef __C_DEQUE_C -extern GB_DESC CDequeDesc[]; -extern GB_DESC CStackDesc[]; -extern GB_DESC CQueueDesc[]; -extern GB_DESC CPrioQueueDesc[]; +extern GB_DESC CDeque[]; +extern GB_DESC CStack[]; +extern GB_DESC CQueue[]; +extern GB_DESC CPrioQueue[]; #endif #endif /* !__C_DEQUE_H */ - diff --git a/main/lib/data/c_list.c b/main/lib/data/c_list.c index 7483cea2f..3029d836b 100644 --- a/main/lib/data/c_list.c +++ b/main/lib/data/c_list.c @@ -30,8 +30,6 @@ #define CHUNK_SIZE 16 -//#define DEBUG_ENUMERATOR - /* * List implementation properties: * + Increase cache locality by saving CHUNK_SIZE values inside a single @@ -39,6 +37,8 @@ * + A special algorithm to re-arrange the values in a chunk guarantees some * properties we can use to speed up the critical paths, such as * traversals. + * + Cached references ('anchors') in the list which can be used calculate + * the best starting point for a traversal to a given index. * - Fragmentation in the middle chunks in a list because of the (O) * postulate of the rearrangement algorithm. */ @@ -54,6 +54,7 @@ typedef struct { typedef struct { CHUNK *ck; int idx; /* Absolute index into ->ck->var */ + int lgi; /* Absolute list global index [-Count; Count - 1] */ } VAL; typedef struct { @@ -61,6 +62,7 @@ typedef struct { LIST list; /* Beginning of linked CHUNKs */ VAL current; /* Current element */ size_t count; /* Do not iterate over all elements to get this */ + int autonorm; /* Automatically normalise indices */ } CLIST; static void CHUNK_init(CHUNK *ck) @@ -93,11 +95,25 @@ static inline int CHUNK_is_first(CLIST *list, CHUNK *ck) return list->list.next == &ck->list; } +static inline int VAL_is_first(CLIST *list, VAL *val) +{ + CHUNK *ck = val->ck; + + return CHUNK_is_first(list, ck) && val->idx == ck->first; +} + static inline int CHUNK_is_last(CLIST *list, CHUNK *ck) { return list->list.prev == &ck->list; } +static inline int VAL_is_last(CLIST *list, VAL *val) +{ + CHUNK *ck = val->ck; + + return CHUNK_is_last(list, ck) && val->idx == ck->last; +} + static void CHUNK_free_all(CHUNK *ck) { int i; @@ -124,6 +140,7 @@ BEGIN_METHOD_VOID(List_new) LIST_init(&THIS->list); THIS->current.ck = NULL; THIS->count = 0; + THIS->autonorm = 0; END_METHOD @@ -158,38 +175,106 @@ static inline int VAL_is_equal(VAL *v1, VAL *v2) return v1->ck == v2->ck && v1->idx == v2->idx; } +/* + * A VAL carries an absolute index of the element it represents in the list. + * This must be updated by each of the functions that modified a VAL because + * they are trusted by traversing algorithms (which have access to Current + * and the enumerators) when looking for the shortest around through the + * list. + * + * The index can be positive or negative. We need some magic to update it + * correctly everytime and with every implementation of C (that's about the + * sign of the result from a % operation). + * + * (Be sure to compile this code with a high optimisation level.) + */ + +#ifndef sgn +# define sgn(x) \ +({ \ + int __x = (x); \ + \ + __x < 0 ? -1 : (__x ? 1 : 0); \ +}) +#endif + +/* One's complement abs() */ +#define onesabs(x) \ +({ \ + int __x = (x); \ + \ + __x < 0 ? ~__x : __x; \ +}) + +/* Get corresponding non-negative index over the list */ +#define abslgi(list, i) \ +({ \ + CLIST *__l = (list); \ + int __i = (i); \ + \ + __i < 0 ? __l->count + __i : __i; \ +}) + +#define update_lgi(list, val, i) \ +do { \ + CLIST *__l = (list); \ + VAL *__v = (val); \ + int __i = (i); \ + \ + if (!__l->count) { \ + __v->ck = NULL; \ + } else { \ + __v->lgi = (onesabs(__i) % __l->count); \ + if (__i < 0) \ + __v->lgi = ~__v->lgi; \ + } \ +} while (0) + static void CLIST_first(CLIST *list, VAL *buf) { if (!list->count) { buf->ck = NULL; + /* + * The VAL is invalid but we want to clear this anyways. + */ + update_lgi(list, buf, 0); return; } + buf->ck = get_chunk(list->list.next); buf->idx = buf->ck->first; + update_lgi(list, buf, 0); } static void CLIST_last(CLIST *list, VAL *buf) { if (!list->count) { buf->ck = NULL; + update_lgi(list, buf, 0); return; } buf->ck = get_chunk(list->list.prev); buf->idx = buf->ck->last; + update_lgi(list, buf, -1); } /* * Modify 'val' so that it points to the next/prev valid value. 'first' is * used to detect the end of an enumeration, i.e. where no other elements * should be looked for. In this case, NULL is written to val->ck. + * + * Since the LGI is said to be in bounds of [-Count; Count - 1], these + * functions must watch when the list head is traversed and reset the index + * accordingly. */ static void CHUNK_next(CLIST *list, VAL *val) { LIST *node; - /* Try to just update the index. This approach comes from the - * rearrange algorithm. */ + update_lgi(list, val, val->lgi + 1); + + /* Try to just update the index. */ if (val->idx < val->ck->last) { val->idx++; return; @@ -208,6 +293,9 @@ static void CHUNK_next_enum(CLIST *list, VAL *first, VAL *val) LIST *node; assert(first != val); + + update_lgi(list, val, val->lgi + 1); + if (val->idx < ck->last) { val->idx++; if (VAL_is_equal(first, val)) @@ -225,12 +313,15 @@ static void CHUNK_next_enum(CLIST *list, VAL *first, VAL *val) no_next: val->ck = NULL; + update_lgi(list, val, 0); } static void CHUNK_prev(CLIST *list, VAL *val) { LIST *node; + update_lgi(list, val, val->lgi - 1); + if (val->idx > val->ck->first) { val->idx--; return; @@ -247,6 +338,9 @@ static void CHUNK_prev_enum(CLIST *list, VAL *first, VAL *val) LIST *node; assert(first != val); + + update_lgi(list, val, val->lgi - 1); + if (val->idx > ck->first) { val->idx--; if (VAL_is_equal(first, val)) @@ -264,94 +358,186 @@ static void CHUNK_prev_enum(CLIST *list, VAL *first, VAL *val) no_prev: val->ck = NULL; + update_lgi(list, val, 0); } /* - * Random access function. + * Random access function stuff (for CLIST_get()). * Negative 'idx' makes go backwards. The 'val' is filled. Out of bounds is * signalled by val->ck == NULL. + * + * We use all references (i.e. Current and the enumerators) to get a good + * anchor, i.e. a VAL and a direction from where we can get to the desired + * index the shortest way. */ -#ifndef sgn -# define sgn(x) ((x) < 0 ? -1 : ((x) ? 1 : 0)) -#endif - -static void CLIST_get(CLIST *list, int idx, VAL *val) -{ - LIST *node; - CHUNK *ck; - int i; - int dir = sgn(idx); - int off, count; - - /* We do _not_ allow indices to wrap around the end, like we could: - * idx %= list->count. Don't use a loop just to detect that case. */ - i = dir * idx - (dir < 0 ? 1 : 0); - if (i >= list->count) - goto out_of_bounds; - - /* Don't traverse forwards/backwards when the absolute value of - * index is above the half of the number of elements */ - if (i > (list->count - 1) / 2) { - dir = -dir; - i = list->count - (i + 1); /* idx = -(idx + 1) */ - } - - node = &list->list; - /* Compute the offset to node->next or node->prev depending on the - * direction we traverse. This saves an if (dir >= 0) branch - * prediction catastrophe in loop. Still, very awful for caches. */ - off = (dir >= 0 ? offsetof(LIST, next) : offsetof(LIST, prev)); - do { - node = *((LIST **) ((long) node + off)); - assert(node != &list->list); - ck = get_chunk(node); - count = CHUNK_count(ck); - if (i < count) { - val->ck = ck; - if (dir < 0) - val->idx = ck->last - i; - else - val->idx = ck->first + i; - return; - } - i -= count; - } while (node != &list->list); - __builtin_trap(); /* XXX */ - -out_of_bounds: - /* Not enough elements. */ - val->ck = NULL; -} - /* XXX: sizeof(VAL) may never exceed 3*sizeof(intptr_t)! */ struct enum_state { CHUNK *first; VAL next; }; +#define begin_all_references(list) \ +do { \ + CLIST *__list = list; \ + VAL *__vp; \ + void *__ebuf; \ + struct enum_state *__es = NULL; \ + \ + __ebuf = GB.BeginEnum(__list); \ + if (!__list->current.ck) { \ + if (GB.NextEnum()) { \ + __vp = NULL; \ + } else { \ + __es = (struct enum_state *) GB.GetEnum(); \ + __vp = &__es->next; \ + } \ + } else { \ + __vp = &__list->current; \ + } \ + for (; __vp; __vp = GB.NextEnum() ? NULL : \ + (__es = (struct enum_state *) GB.GetEnum(), &__es->next)) { + +#define end_all_references \ + } \ + GB.EndEnum(__ebuf); \ +} while (0) + +struct anchor { + VAL start; + int direction; +}; + +/* 'idx' is required to be non-negative and in bounds at this point. */ +static inline void get_best_anchor(CLIST *list, int idx, struct anchor *buf) +{ + int d, tmp; + + /* Distance from head forwards/backwards/of all references */ + d = idx; + tmp = list->count - 1 - idx; + if (tmp < d) { + d = tmp; + CLIST_last(list, &buf->start); + } else { + CLIST_first(list, &buf->start); + } + begin_all_references(list) { + tmp = abs(abslgi(list, __vp->lgi) - idx); + if (tmp < d) { + d = tmp; + memcpy(&buf->start, __vp, sizeof(buf->start)); + } + } end_all_references; + + buf->direction = sgn(idx - abslgi(list, buf->start.lgi)); +} + +static inline void get_body_forward(CLIST *list, LIST *node, int i, + VAL *val) +{ + CHUNK *ck; + int count; + + while (1) { + ck = get_chunk(node); + count = CHUNK_count(ck); + + if (i < count) { + val->ck = ck; + val->idx = ck->first + i; + return; + } + i -= count; + do + node = node->next; + while (node == &list->list); + } +} + +static inline void get_body_backward(CLIST *list, LIST *node, int i, + VAL *val) +{ + CHUNK *ck; + int count; + + while (1) { + do + node = node->prev; + while (node == &list->list); + ck = get_chunk(node); + count = -CHUNK_count(ck); + + if (i >= count) { + val->ck = ck; + val->idx = ck->last + i + 1; + return; + } + i -= count; + } +} + +static void CLIST_get(CLIST *list, int idx, VAL *val) +{ + LIST *node; + int i, dir; + struct anchor anchor; + + /* Make a non-negative index */ + i = abslgi(list, idx); + /* We do _not_ allow indices to wrap around the end, like we could: + * i %= list->count. Don't use a loop just to detect that case. */ + if (i >= list->count) { + /* Not enough elements. */ + val->ck = NULL; + return; + } + + get_best_anchor(list, i, &anchor); + dir = anchor.direction; + + update_lgi(list, val, idx); + /* Got that index in a reference already? Just copy. */ + if (!dir) { + val->ck = anchor.start.ck; + val->idx = anchor.start.idx; + return; + } + + node = &anchor.start.ck->list; + /* + * Don't start exactly at the given anchor point (possibly in the + * middle of a chunk) but instead at the beginning of the chunk. + * Because of cache spatiality, this is not a great loss and the + * code is simplified much. + */ + i -= abslgi(list, anchor.start.lgi); + i += anchor.start.idx - anchor.start.ck->first; + + /* + * Prevent a if (i < 0) branch prediction catastrophe if we merged + * both algorithms into one loop. + */ + if (i < 0) + get_body_backward(list, node, i, val); + else + get_body_forward(list, node, i, val); +} + BEGIN_METHOD_VOID(List_next) struct enum_state *state = GB.GetEnum(); GB_VARIANT_VALUE *val; - VAL start; /* XXX: Would like to cache that in the enum_state... */ + /* XXX: Would like to cache that in the enum_state but no space left + * there... */ + VAL start; if (!state->first) { /* Beginning */ -#ifdef DEBUG_ENUMERATOR - printf("New enumerator %p\n", state); -#endif CLIST_first(THIS, &state->next); state->first = state->next.ck; } /* No elements left? */ if (!state->next.ck) { -#ifdef DEBUG_ENUMERATOR - /* - * Mark this enumerator invalid. - */ - state->first = NULL; - printf("Leaving enumeration %p\n", state); -#endif GB.StopEnum(); return; } @@ -375,18 +561,12 @@ BEGIN_METHOD_VOID(ListBackwards_next) VAL start; if (!state->first) { /* Beginning */ -#ifdef DEBUG_ENUMERATOR - printf("New enumerator %p\n", state); -#endif CLIST_last(THIS, &state->next); state->first = state->next.ck; } /* No elements left? */ if (!state->next.ck) { -#ifdef DEBUG_ENUMERATOR state->first = NULL; - printf("Leaving enumeration of %p\n", state); -#endif GB.StopEnum(); return; } @@ -399,11 +579,23 @@ BEGIN_METHOD_VOID(ListBackwards_next) END_METHOD +static inline int normalise_index(CLIST *list, int index) +{ + int i; + + i = onesabs(index) % list->count; + if (index < 0) + i = ~i; + return i; +} + BEGIN_METHOD(List_get, GB_INTEGER index) int index = VARG(index); VAL val; + if (THIS->autonorm) + index = normalise_index(THIS, index); CLIST_get(THIS, index, &val); if (!val.ck) { GB.Error(GB_ERR_BOUND); @@ -418,6 +610,8 @@ BEGIN_METHOD(List_put, GB_VARIANT var; GB_INTEGER index) int index = VARG(index); VAL val; + if (THIS->autonorm) + index = normalise_index(THIS, index); CLIST_get(THIS, index, &val); if (!val.ck) { GB.Error(GB_ERR_BOUND); @@ -433,8 +627,8 @@ END_METHOD * * The following postulates shall be met by the algorithms: * - * (V) Value. If an element other than that a VAL refers to is removed, or - * an element is added, the reference shall be modified according to the + * (V) Value. If an element other than that a VAL refers to is removed or an + * element is added, the reference shall be modified according to the * operation, i.e. it shall stay pointing to the particular value it has * pointed to before. References are value-bound. * @@ -459,6 +653,8 @@ END_METHOD * last chunk has all elements aligned to its beginning. For the special * case of only one chunk in the List (the 'sole chunk'), it is not * specially aligned but its initial element is at CHUNK_SIZE/2-1. + * Should a chunk be allocated and linked into the middle of the list, + * its initial element shall also be at CHUNK_SIZE/2-1. * * (L) Least Copy. Rearrangement for any non-aligned chunk shall strive for * the least copy operations. @@ -471,26 +667,7 @@ END_METHOD * be improved so it remains in the first place. */ -/* XXX: Mind the anatomy of this construct! As a 'for' loop it'd be way too - * ugly. */ -#define begin_all_references(list) \ -do { \ - CLIST *__list = list; \ - VAL *__vp; \ - void *__ebuf; \ - struct enum_state *__es = NULL; \ - \ - __ebuf = GB.BeginEnum(__list); \ - for (__vp = &__list->current; __vp; __vp = !GB.NextEnum() ? \ - (__es = (struct enum_state *) GB.GetEnum(),\ - &__es->next) : NULL) { - -#define end_all_references \ - } \ - GB.EndEnum(__ebuf); \ -} while (0) - -static void CLIST_append(CLIST *list, GB_VARIANT *val) +static void CLIST_append(CLIST *list, GB_VARIANT *var) { CHUNK *ck; @@ -508,22 +685,19 @@ static void CLIST_append(CLIST *list, GB_VARIANT *val) ck->first--; } /* (C), (O) */ - GB.StoreVariant(val, &ck->var[ck->first]); + GB.StoreVariant(var, &ck->var[ck->first]); list->count++; /* (V) */ begin_all_references(list) { -#ifdef DEBUG_ENUMERATOR - if (__es && !__es->first) - printf("Caught spurious enumerator %p\n", __es); -#endif - if (__vp->ck != ck) - continue; - __vp->idx++; + if (__vp->lgi >= 0) + __vp->lgi++; + if (__vp->ck == ck) + __vp->idx++; } end_all_references; } -static void CLIST_prepend(CLIST *list, GB_VARIANT *val) +static void CLIST_prepend(CLIST *list, GB_VARIANT *var) { CHUNK *ck; @@ -541,9 +715,220 @@ static void CLIST_prepend(CLIST *list, GB_VARIANT *val) ck->last++; } /* (C), (O) */ - GB.StoreVariant(val, &ck->var[ck->last]); + GB.StoreVariant(var, &ck->var[ck->last]); list->count++; - /* (V): nothing */ + + /* (V) */ + begin_all_references(list) { + if (__vp->lgi < 0) + __vp->lgi--; + } end_all_references; +} + +/* + * With the VAL_append()/prepend() functions it is a little more difficult. + * We have to distinguish three cases (processed in this order): + * 1) There is space for the operation in this chunk: so shift elements and + * insert the new value; + * 1a) If the element is to be appended/prepended after/before the + * last/first element, do not shift; + * 2) There is space _immediately_ free in a neighbour chunk for the + * operation (for append it's the next chunk, for prepend the previous + * one), i.e. we don't need to shift the neighbour chunk, then shift + * values into this chunk and insert the new value. This may never cross + * the list head!; + * 2a) See 1a); + * 3) If none of the previous things worked, allocate a new chunk and shift + * in there; + * 3a) See 1a); + * + * Point 2) didn't shift values in the neighbour because this could go + * infinitely through the list so we just allocate a new chunk to do it fast + * and easily. + */ + +static void VAL_append(CLIST *list, VAL *val, GB_VARIANT *var) +{ + CHUNK *ck, *next; + int s, n, shifted_to_next = 0; + GB_VARIANT_VALUE *buf; + VAL back; + + ck = val->ck; + /* Currently not (L). We only shift towards the end. */ + if (ck->last < CHUNK_SIZE - 1) { /* 1) */ + ck->last++; + if (val->idx == ck->last - 1) { /* 1a) */ + buf = &ck->var[ck->last]; + } else { + shift: + s = val->idx + 1; + n = ck->last - s; + memmove(&ck->var[s + 1], &ck->var[s], + n * sizeof(ck->var[0])); + buf = &ck->var[s]; + } + } else { + LIST *node = ck->list.next; + + /* 2) */ + if (node == &list->list) + goto add_new_chunk; + + next = get_chunk(node); + if (next->first) { + next->first--; + if (val->idx == ck->last) { /* 2a) */ + buf = &next->var[next->first]; + goto have_buf; + } + shift_to_next: + shifted_to_next = 1; + memcpy(&next->var[next->first], &ck->var[ck->last], + sizeof(ck->var[0])); + goto shift; + } else { /* 3) */ + add_new_chunk: + next = CHUNK_new(); + next->first = next->last = CHUNK_SIZE / 2 - 1; + LIST_append(&ck->list, &next->list); + if (val->idx == ck->last) { /* 3a) */ + buf = &next->var[next->first]; + goto have_buf; + } + goto shift_to_next; + } + } + +have_buf: + bzero(buf, sizeof(*buf)); + buf->type = GB_T_NULL; + GB.StoreVariant(var, buf); + list->count++; + + int lgi, vlgi; + + /* + * Nasty: When appending to the last element iff it has a negative + * LGI, i.e. val->lgi == -1, then we must not produce a 'lgi' of + * value 0. For a positive LGI of the last element, we just + * incremented list->count so that's no problem. + */ + if (val->lgi == -1) + lgi = abslgi(list, -1); + else + lgi = normalise_index(list, abslgi(list, val->lgi + 1)); + memcpy(&back, val, sizeof(back)); + begin_all_references(list) { + vlgi = abslgi(list, __vp->lgi); + + if (vlgi <= lgi && __vp->lgi < 0) + __vp->lgi--; + else if (vlgi > lgi && __vp->lgi >= 0) + __vp->lgi++; + __vp->lgi = normalise_index(list, __vp->lgi); + + if (__vp->ck == back.ck) { + if (shifted_to_next && __vp->idx == back.ck->last) { + __vp->ck = next; + __vp->idx = next->first; + } else if (__vp->idx > back.idx) { + __vp->idx++; + } + } + } end_all_references; +} + +static void VAL_prepend(CLIST *list, VAL *val, GB_VARIANT *var) +{ + CHUNK *ck, *prev; + int s, n, shifted_to_prev = 0; + GB_VARIANT_VALUE *buf; + VAL back; + + ck = val->ck; + /* Not (L) */ + if (ck->first) { /* 1) */ + ck->first--; + if (val->idx == ck->first + 1) { /* 1a) */ + buf = &ck->var[ck->first]; + } else { + shift: + s = ck->first + 1; + n = val->idx - s; + memmove(&ck->var[s - 1], &ck->var[s], + n * sizeof(ck->var[0])); + buf = &ck->var[s + n - 1]; + } + } else { + LIST *node = ck->list.prev; + + /* 2) */ + if (node == &list->list) + goto add_new_chunk; + + prev = get_chunk(node); + if (prev->last < CHUNK_SIZE - 1) { + prev->last++; + if (val->idx == ck->first) { /* 2a) */ + buf = &prev->var[prev->last]; + goto have_buf; + } + shift_to_prev: + shifted_to_prev = 1; + memcpy(&prev->var[prev->last], &ck->var[ck->first], + sizeof(ck->var[0])); + goto shift; + } else { /* 3) */ + add_new_chunk: + prev = CHUNK_new(); + prev->first = prev->last = CHUNK_SIZE / 2 - 1; + LIST_prepend(&ck->list, &prev->list); + if (val->idx == ck->first) { /* 3a) */ + buf = &prev->var[prev->last]; + goto have_buf; + } + goto shift_to_prev; + } + } + +have_buf: + bzero(buf, sizeof(*buf)); + buf->type = GB_T_NULL; + GB.StoreVariant(var, buf); + list->count++; + + int lgi, vlgi; + + /* + * We have a similar case here as in VAL_append() but the other way + * around: if the element is the new first one, we must check if + * val->lgi is 0, it then may stay 0. If it was -list->count, + * everything is fine because of list->count++ above. + */ + if (!val->lgi) + lgi = 0; + else + lgi = normalise_index(list, abslgi(list, val->lgi - 1)); + memcpy(&back, val, sizeof(back)); + begin_all_references(list) { + vlgi = abslgi(list, __vp->lgi); + + if (vlgi <= lgi && __vp->lgi < 0) + __vp->lgi--; + else if (vlgi >= lgi && __vp->lgi >= 0) + __vp->lgi++; + __vp->lgi = normalise_index(list, __vp->lgi); + + if (__vp->ck == back.ck) { + if (shifted_to_prev && __vp->idx == back.ck->first) { + __vp->ck = prev; + __vp->idx = prev->last; + } else if (__vp->idx < back.idx) { + __vp->idx--; + } + } + } end_all_references; } static void CLIST_take(CLIST *list, VAL *val, GB_VARIANT_VALUE *buf) @@ -601,46 +986,72 @@ normal: memmove(&ck->var[dst], &ck->var[src], size); no_move: - /* Don't forget to remove the phantom value */ + /* Don't forget to remove the phantom value (overriding the actual + * data with zeros to be sure from leakage) */ + bzero(&ck->var[phantom], sizeof(ck->var[0])); ck->var[phantom].type = GB_T_NULL; list->count--; /* Don't accidentally erase information if 'val' is a reference - * itself. */ + * itself because then once __vp == val and __vp will be changed. */ memcpy(&back, val, sizeof(back)); begin_all_references(list) { - if (__es && !__es->first) { -#ifdef DEBUG_ENUMERATOR - printf("Caught spurious enumerator %p\n", __es); +#ifdef DEBUG_ME + printf(": in: %p -> %d (%d) %d %d\n", __vp, __vp->idx, + __vp->lgi, back.idx, src < dst); #endif - continue; + + /* The LGI stuff is pretty much independent of the ->ck and + * ->idx code and conditions. We only care about negative + * LGIs basically because non-negative ones automagically + * stay correct according to (B). */ + int vlgi = abslgi(list, __vp->lgi); + int blgi = abslgi(list, back.lgi); + + if (vlgi >= blgi) { + /* If this reference is taken and it's the last + * element of the list, then set it to zero. Kind + * of a nasty condition to occur but this is the + * only difficulty here because of (B). */ + if (UNLIKELY(vlgi == list->count && vlgi == blgi)) + __vp->lgi = 0; + else if (__vp->lgi < 0) + __vp->lgi++; } + if (__vp->ck != back.ck) continue; -#ifdef DEBUG_ME - printf(": in: %p -> %d %d %d\n", __vp, __vp->idx, back.idx, - src < dst); -#endif + /* (B) */ if (__vp->idx == back.idx) { if (!list->count) { __vp->ck = NULL; - } else if (gets_empty || src < dst) { - CHUNK_next(list, __vp); - } else { - __vp->idx--; - CHUNK_next(list, __vp); + continue; + } else if (gets_empty) { + goto next_chunk; + } else if (src < dst) { + /* According to ck->first++ above which + * happens only when src < dst. */ + __vp->idx++; } - /* (V) */ - } else if (__vp->idx > back.idx) { - __vp->idx--; - CHUNK_next(list, __vp); } + /* (V) */ + LIST *node; + + if (__vp->idx > __vp->ck->last) { + next_chunk: + if ((node = __vp->ck->list.next) == &list->list) + node = node->next; + __vp->ck = get_chunk(node); + __vp->idx = __vp->ck->first; + } + #ifdef DEBUG_ME - printf(": out: %p -> %d (%p,%d,%d)\n", __vp, __vp->idx, - __vp->ck, __vp->ck ? __vp->ck->first : 0, - __vp->ck ? __vp->ck->last : 0); + printf(": out: %p -> %d (%d) (%p,%d,%d)\n", __vp, __vp->idx, + __vp->lgi, __vp->ck, __vp->ck ? + __vp->ck->first : 0, __vp->ck ? + __vp->ck->last : 0); #endif } end_all_references; if (gets_empty) { @@ -672,12 +1083,16 @@ BEGIN_METHOD(List_Take, GB_INTEGER index) VAL val; GB_VARIANT_VALUE buf; + int index; if (MISSING(index)) { CHECK_RAISE_CURRENT(); CLIST_take(THIS, &THIS->current, &buf); } else { - CLIST_get(THIS, VARG(index), &val); + index = VARG(index); + if (THIS->autonorm) + index = normalise_index(THIS, index); + CLIST_get(THIS, index, &val); if (!val.ck) { GB.Error(GB_ERR_BOUND); return; @@ -691,23 +1106,36 @@ BEGIN_METHOD(List_Take, GB_INTEGER index) END_METHOD -#define IMPLEMENT_Move(which, magic) \ -BEGIN_METHOD_VOID(List_Move ## which) \ - \ - if (!THIS->count) { \ - GB.Error("No elements"); \ - return; \ - } \ - magic; \ - \ +#define IMPLEMENT_Move(which, magic) \ +BEGIN_METHOD_VOID(List_Move ## which) \ + \ + if (!THIS->count) { \ + GB.Error("No elements"); \ + return; \ + } \ + magic; \ + \ END_METHOD IMPLEMENT_Move(Next, if (!CHECK_CURRENT())CLIST_first(THIS, &THIS->current); - CHUNK_next(THIS, &THIS->current)) + CHUNK_next(THIS, &THIS->current);) IMPLEMENT_Move(Prev, if (!CHECK_CURRENT()) CLIST_last(THIS, &THIS->current); - CHUNK_prev(THIS, &THIS->current)) -IMPLEMENT_Move(First, CLIST_first(THIS, &THIS->current)) -IMPLEMENT_Move(Last, CLIST_last(THIS, &THIS->current)) + CHUNK_prev(THIS, &THIS->current);) +IMPLEMENT_Move(First, CLIST_first(THIS, &THIS->current);) +IMPLEMENT_Move(Last, CLIST_last(THIS, &THIS->current);) + +BEGIN_METHOD(List_MoveTo, GB_INTEGER index) + + int index = VARG(index); + + if (THIS->autonorm) + index = normalise_index(THIS, index); + + CLIST_get(THIS, index, &THIS->current); + if (!THIS->current.ck) + GB.Error(GB_ERR_BOUND); + +END_METHOD /* * Modify 'val' to point to the next/prev value equal to 'comp'. If nothing @@ -787,7 +1215,17 @@ IMPLEMENT_Find(Prev, backward, IMPLEMENT_Find(First, forward, CLIST_first(THIS, &THIS->current)) IMPLEMENT_Find(Last, backward, CLIST_last(THIS, &THIS->current)) -BEGIN_PROPERTY(List_Current) +BEGIN_PROPERTY(List_AutoNormalize) + + if (READ_PROPERTY) { + GB.ReturnBoolean(THIS->autonorm); + return; + } + THIS->autonorm = VPROP(GB_BOOLEAN); + +END_PROPERTY + +BEGIN_PROPERTY(ListItem_Value) GB_VARIANT_VALUE *val; @@ -801,13 +1239,38 @@ BEGIN_PROPERTY(List_Current) END_PROPERTY +BEGIN_PROPERTY(ListItem_Index) + + int index; + + if (READ_PROPERTY) { + GB.ReturnInteger(THIS->current.lgi); + return; + } + if (THIS->autonorm) + index = normalise_index(THIS, VPROP(GB_INTEGER)); + else + index = VPROP(GB_INTEGER); + CLIST_get(THIS, index, &THIS->current); + if (!THIS->current.ck) + GB.Error(GB_ERR_BOUND); + +END_PROPERTY + BEGIN_PROPERTY(List_Count) GB.ReturnInteger(THIS->count); END_PROPERTY -GB_DESC CListDesc[] = { +BEGIN_PROPERTY(List_Current) + + CHECK_RAISE_CURRENT(); + GB.ReturnSelf(THIS); + +END_PROPERTY + +GB_DESC CList[] = { GB_DECLARE("List", sizeof(CLIST)), GB_METHOD("_new", NULL, List_new, NULL), @@ -827,6 +1290,7 @@ GB_DESC CListDesc[] = { GB_METHOD("MovePrevious", NULL, List_MovePrev, NULL), GB_METHOD("MoveFirst", NULL, List_MoveFirst, NULL), GB_METHOD("MoveLast", NULL, List_MoveLast, NULL), + GB_METHOD("MoveTo", NULL, List_MoveTo, "(Index)i"), GB_METHOD("FindNext", NULL, List_FindNext, "(Value)v"), GB_METHOD("FindPrev", NULL, List_FindPrev, "(Value)v"), @@ -834,19 +1298,17 @@ GB_DESC CListDesc[] = { GB_METHOD("FindFirst", NULL, List_FindFirst, "(Value)v"), GB_METHOD("FindLast", NULL, List_FindLast, "(Value)v"), - /* - * XXX: Shouldn't this be rather more exposed as the virtual - * container it actually is? But with an absolute index, of - * course. But can this be done efficiently? - */ - GB_PROPERTY("Current", "v", List_Current), + GB_PROPERTY("AutoNormalize", "b", List_AutoNormalize), + GB_PROPERTY("Current", ".List.Item", List_Current), + GB_PROPERTY("Value", "v", ListItem_Value), + GB_PROPERTY("Index", "i", ListItem_Index), GB_PROPERTY_READ("Count", "i", List_Count), GB_PROPERTY_SELF("Backwards", ".List.Backwards"), GB_END_DECLARE }; -GB_DESC CListBackwardsDesc[] = { +GB_DESC CListBackwards[] = { GB_DECLARE(".List.Backwards", 0), GB_VIRTUAL_CLASS(), @@ -854,3 +1316,49 @@ GB_DESC CListBackwardsDesc[] = { GB_END_DECLARE }; + +BEGIN_METHOD(ListItem_Append, GB_VARIANT value) + + VAL_append(THIS, &THIS->current, ARG(value)); + +END_METHOD + +BEGIN_METHOD(ListItem_Prepend, GB_VARIANT value) + + VAL_prepend(THIS, &THIS->current, ARG(value)); + +END_METHOD + +BEGIN_PROPERTY(ListItem_IsFirst) + + GB.ReturnBoolean(VAL_is_first(THIS, &THIS->current)); + +END_PROPERTY + +BEGIN_PROPERTY(ListItem_IsLast) + + GB.ReturnBoolean(VAL_is_last(THIS, &THIS->current)); + +END_PROPERTY + +BEGIN_PROPERTY(ListItem_IsValid) + + GB.ReturnBoolean(!!CHECK_CURRENT()); + +END_PROPERTY + +GB_DESC CListItem[] = { + GB_DECLARE(".List.Item", 0), + GB_VIRTUAL_CLASS(), + + GB_METHOD("Append", NULL, ListItem_Append, "(Value)v"), + GB_METHOD("Prepend", NULL, ListItem_Prepend, "(Value)v"), + + GB_PROPERTY_READ("IsFirst", "b", ListItem_IsFirst), + GB_PROPERTY_READ("IsLast", "b", ListItem_IsLast), + GB_PROPERTY_READ("IsValid", "b", ListItem_IsValid), + GB_PROPERTY_READ("Index", "i", ListItem_Index), + GB_PROPERTY("Value", "v", ListItem_Value), + + GB_END_DECLARE +}; diff --git a/main/lib/data/c_list.h b/main/lib/data/c_list.h index c9104fb81..dd0c453ef 100644 --- a/main/lib/data/c_list.h +++ b/main/lib/data/c_list.h @@ -28,10 +28,9 @@ extern GB_INTERFACE GB; #ifndef __C_LIST_C -extern GB_DESC CListDesc[]; -extern GB_DESC CListBackwardsDesc[]; - -extern void CLIST_exit(void); +extern GB_DESC CList[]; +extern GB_DESC CListBackwards[]; +extern GB_DESC CListItem[]; #endif #endif /* !__C_LIST_H */ diff --git a/main/lib/data/main.c b/main/lib/data/main.c index a607ce0bb..37ff1bb9f 100644 --- a/main/lib/data/main.c +++ b/main/lib/data/main.c @@ -24,20 +24,24 @@ #include "c_list.h" #include "c_deque.h" #include "c_circular.h" +#include "c_avltree.h" #include "main.h" GB_INTERFACE GB EXPORT; GB_DESC *GB_CLASSES[] EXPORT = { - CListDesc, - CListBackwardsDesc, + CList, + CListBackwards, + CListItem, - CDequeDesc, - CStackDesc, - CQueueDesc, - CPrioQueueDesc, + CDeque, + CStack, + CQueue, + CPrioQueue, - CCircularDesc, + CCircular, + +// CAvlTree, NULL };