30. Ring data structure

30.1. Introduction

.source: rings are derived from the earlier use of double-ended queues (deques). RB found that most of the deque features were unused (see item 6 of mail.richard.1996-03-25.16-02) and so the simple doubly-linked list structure of rings suffices.

30.2. Description

typedef RingStruct *Ring

.def.ring: Rings are circular doubly-linked lists of ring “nodes”. The nodes are fields of structures which are the “elements” of the ring.

Ring node structures (RingStruct) are inlined in the structures on the ring, like this:

typedef struct FooStruct *Foo;     /* the element type */
typedef struct FooStruct {         /* the element structure */
  int baz, bim;
  RingStruct ring;                 /* the ring node */
  float bip, bop;
} FooStruct;

This arrangement means that they do not need to be managed separately. This is especially useful in avoiding re-entrancy and bootstrapping problems in the memory manager. Rings also provide flexible insertion and deletion because the entire ring can be found from any node.

In the MPS, rings are used to connect a “parent” structure (such as a Arena) to a number of “child” structures (such as Pool), as shown in .fig.ring.

.fig.ring: A ring of Child objects owned by a Parent object.

[missing figure]

.fig.empty: An empty ring of Child objects owned by a Parent object.

[missing figure]

.def.singleton: A “singleton” ring is a ring containing one node, whose previous and next nodes are itself (see .fig.single).

.fig.single: A singleton Child object not on any ring.

[missing figure]

.fig.elt: How RING_ELT() gets a parent pointer from a node pointer.

[missing figure]

30.3. Interface

30.3.1. Init / Finish

void RingInit(Ring ring)

.init: Rings are initialized with the RingInit() function. They are initialized to be a singleton ring (.def.singleton).

void RingFinish(Ring ring)

.finish: Rings are finished with the RingFinish() function. A ring must be a singleton ring before it can be finished (it is an error to attempt to finish a non-singleton ring).

30.3.2. Checking

Bool RingCheck(Ring ring)

.check: RingCheck() is the check function for rings. See design.mps.check).

Bool RingCheckSingle(Ring ring)

.check.single: RingCheckSingle() is a check function that additionally checks that ring is a singleton (see .def.singleton).

Bool RingIsSingle(Ring ring)

.is.single: Return TRUE if ring is a singleton (see .def.singleton).

Count RingLength(Ring ring)

.length: Return the number of elements in the ring, not counting ring itself. This therefore returns 0 for singleton rings, and for parent-children rings it returns the number of children.

30.3.3. Iteration

RING_FOR(node, ring, next)

.for: A macro is used for iterating over the elements in a ring. This macro is called RING_FOR(). RING_FOR() takes three arguments. The first is an iteration variable: node. The second is the “parent” element in the ring: ring. The third is a variable used by the iterator for working state (it holds a pointer to the next node): next. All arguments must be of type Ring. The node and next variables must be declared and in scope already. All elements except for the “parent” element are iterated over. The macro expands to a for statement. During execution of the loop, the node variable (the first argument to the macro) will be the value of successive elements in the Ring (at the beginning of the statement in the body of the loop).

.for.error: It is an error (possibly unchecked) for the node and next variables to be modified except implicitly by using this iterator.

.for.safe: It is safe to delete the current node during the iteration.

.for.ex: An example:

Ring node, nextNode;
RING_FOR(node, &parent->childRing, nextNode) {
  Child child = RING_ELT(Child, ParentRing, node);
  foo(child);
}

.for.ex.elt: Notice the idiomatic use of RING_ELT() which is almost universal when using RING_FOR().

30.3.4. Element access

Ring RingNext(Ring ring)

.next: RingNext() returns the next node in the ring.

Ring RingPrev(Ring ring)

.prev: RingPrev() returns the previous node in the ring.

RING_ELT(type, field, node)

.elt: RING_ELT() is a macro that converts a pointer to a ring structure into a pointer to the enclosing parent structure. RING_ELT() has three arguments which are, in order: type, the type of a pointer to the enclosing structure, field, the name of the ring structure field within it, ring, the ring node. The result is a pointer to the enclosing structure.

Note

RING_ELT() does not work for arrays of rings.

30.3.5. Append / Remove

void RingAppend(ring, new)

.append: RingAppend() appends a singleton ring to a ring (such that the newly added element will be last in the iteration sequence).

void RingInsert(Ring ring, Ring new)

.insert: RingInsert() adds a singleton ring to a ring (such that the newly added element will be first in the iteration sequence).

void RingRemove(Ring old)

.remove: RingRemove() removes an element from a ring. The newly removed element becomes a singleton ring. It is an error for the element to already be a singleton.

.improve.join: It would be possible to add a RingJoin() operation that joined two rings. This is not done as it is not required.

30.4. Naming

.naming: By convention, when one structure Parent contains one ring of Child structures, the field in Parent is usually known as childRing, and the field in Child is known as parentRing. If the Parent structure contains more than one ring of Child structures, then they should have names like allocatedChildRing and freeChildRing.

.naming.rule.break: Note the slight abuse of naming convention, in that the ring members have names ending in Ring rather than RingStruct.

30.5. Deques

This section documents where rings differ significantly from deques.

.head: Deques used a distinguished head structure for the head of the ring. Rings still have a separate head structure, but it is not distinguished by type.

30.6. Defects

This section documents known defects with the current design.

.app_for.misuse: It is easy to pass RingAppend() and RING_FOR() the arguments in the wrong order as all the arguments have the same type.

.check.improve: There is no method for performing a full integrity check. This could be added.