Graphviz 12.0.1~dev.20240715.2254
Loading...
Searching...
No Matches
node.c
Go to the documentation of this file.
1
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * https://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: Details at https://graphviz.org
12 *************************************************************************/
13
14#include <assert.h>
15#include <cgraph/alloc.h>
16#include <cgraph/cghdr.h>
17#include <cgraph/node_set.h>
18#include <cgraph/unreachable.h>
19#include <stdbool.h>
20#include <stdlib.h>
21
23{
24 Agsubnode_t *sn;
25
26 sn = node_set_find(g->n_id, id);
27 return sn ? sn->node : NULL;
28}
29
30static Agnode_t *agfindnode_by_name(Agraph_t * g, char *name)
31{
32 IDTYPE id;
33
34 if (agmapnametoid(g, AGNODE, name, &id, false))
35 return agfindnode_by_id(g, id);
36 else
37 return NULL;
38}
39
41{
42 Agsubnode_t *sn;
43 sn = dtfirst(g->n_seq);
44 return sn ? sn->node : NULL;
45}
46
48{
49 Agsubnode_t *sn;
50 sn = agsubrep(g, n);
51 if (sn) sn = dtnext(g->n_seq, sn);
52 return sn ? sn->node : NULL;
53}
54
56{
57 Agsubnode_t *sn;
58 sn = dtlast(g->n_seq);
59 return sn ? sn->node : NULL;
60}
61
63{
64 Agsubnode_t *sn;
65 sn = agsubrep(g, n);
66 if (sn) sn = dtprev(g->n_seq, sn);
67 return sn ? sn->node : NULL;
68}
69
70
71/* internal node constructor */
72static Agnode_t *newnode(Agraph_t * g, IDTYPE id, uint64_t seq)
73{
74 Agnode_t *n;
75
76 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
77
78 n = agalloc(g, sizeof(Agnode_t));
79 AGTYPE(n) = AGNODE;
80 AGID(n) = id;
81 AGSEQ(n) = seq & SEQ_MASK;
82 n->root = agroot(g);
83 if (agroot(g)->desc.has_attrs)
84 (void)agbindrec(n, AgDataRecName, sizeof(Agattr_t), false);
85 /* nodeattr_init and method_init will be called later, from the
86 * subgraph where the node was actually created, but first it has
87 * to be installed in all the (sub)graphs up to root. */
88 return n;
89}
90
91static void installnode(Agraph_t * g, Agnode_t * n)
92{
93 Agsubnode_t *sn;
94 size_t osize;
95 (void)osize;
96
97 assert(node_set_size(g->n_id) == (size_t)dtsize(g->n_seq));
98 osize = node_set_size(g->n_id);
99 if (g == agroot(g)) sn = &(n->mainsub);
100 else sn = agalloc(g, sizeof(Agsubnode_t));
101 sn->node = n;
102 node_set_add(g->n_id, sn);
103 dtinsert(g->n_seq, sn);
104 assert(node_set_size(g->n_id) == (size_t)dtsize(g->n_seq));
105 assert(node_set_size(g->n_id) == osize + 1);
106}
107
109{
110 Agraph_t *par;
111 installnode(g, n);
112 if ((par = agparent(g)))
113 installnodetoroot(par, n);
114}
115
116static void initnode(Agraph_t * g, Agnode_t * n)
117{
118 if (agroot(g)->desc.has_attrs)
119 agnodeattr_init(g,n);
120 agmethod_init(g, n);
121}
122
123/* external node constructor - create by id */
124Agnode_t *agidnode(Agraph_t * g, IDTYPE id, int cflag)
125{
126 Agraph_t *root;
127 Agnode_t *n;
128
129 n = agfindnode_by_id(g, id);
130 if (n == NULL && cflag) {
131 root = agroot(g);
132 if ((g != root) && ((n = agfindnode_by_id(root, id)))) /*old */
133 agsubnode(g, n, 1); /* insert locally */
134 else {
135 if (agallocid(g, AGNODE, id)) { /* new */
136 n = newnode(g, id, agnextseq(g, AGNODE));
137 installnodetoroot(g, n);
138 initnode(g, n);
139 } else
140 n = NULL; /* allocid for new node failed */
141 }
142 }
143 /* else return probe result */
144 return n;
145}
146
147Agnode_t *agnode(Agraph_t * g, char *name, int cflag)
148{
149 Agraph_t *root;
150 Agnode_t *n;
151 IDTYPE id;
152
153 root = agroot(g);
154 /* probe for existing node */
155 if (agmapnametoid(g, AGNODE, name, &id, false)) {
156 if ((n = agfindnode_by_id(g, id)))
157 return n;
158
159 /* might already exist globally, but need to insert locally */
160 if (cflag && (g != root) && ((n = agfindnode_by_id(root, id)))) {
161 return agsubnode(g, n, 1);
162 }
163 }
164
165 if (cflag && agmapnametoid(g, AGNODE, name, &id, true)) { /* reserve id */
166 n = newnode(g, id, agnextseq(g, AGNODE));
167 installnodetoroot(g, n);
168 initnode(g, n);
169 assert(agsubrep(g,n));
170 agregister(g, AGNODE, n); /* register in external namespace */
171 return n;
172 }
173
174 return NULL;
175}
176
177/* removes image of node and its edges from graph.
178 caller must ensure n belongs to g. */
179void agdelnodeimage(Agraph_t * g, Agnode_t * n, void *ignored)
180{
181 Agedge_t *e, *f;
182 Agsubnode_t template = {0};
183 template.node = n;
184
185 (void)ignored;
186 for (e = agfstedge(g, n); e; e = f) {
187 f = agnxtedge(g, e, n);
188 agdeledgeimage(g, e, 0);
189 }
190 /* If the following lines are switched, switch the discpline using
191 * free_subnode below.
192 */
194 dtdelete(g->n_seq, &template);
195}
196
198{
199 Agedge_t *e, *f;
200
201 if (!agfindnode_by_id(g, AGID(n)))
202 return FAILURE; /* bad arg */
203 if (g == agroot(g)) {
204 for (e = agfstedge(g, n); e; e = f) {
205 f = agnxtedge(g, e, n);
206 agdeledge(g, e);
207 }
208 if (g->desc.has_attrs)
210 agmethod_delete(g, n);
211 agrecclose((Agobj_t *) n);
212 agfreeid(g, AGNODE, AGID(n));
213 }
214 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agdelnodeimage, NULL, false) == SUCCESS) {
215 if (g == agroot(g))
216 agfree(g, n);
217 return SUCCESS;
218 } else
219 return FAILURE;
220}
221
222static void dict_relabel(Agraph_t *ignored, Agnode_t *n, void *arg) {
223 (void)ignored;
224
225 Agraph_t *g;
226 uint64_t new_id;
227
228 g = agraphof(n);
229 new_id = *(uint64_t *) arg;
230 Agsubnode_t *key = agsubrep(g, n);
231 assert(key != NULL && "node being renamed does not exist");
232 node_set_remove(g->n_id, key->node->base.tag.id);
233 AGID(key->node) = new_id;
234 node_set_add(g->n_id, key);
235 /* because all the subgraphs share the same node now, this
236 now requires a separate deletion and insertion phase */
237}
238
239int agrelabel_node(Agnode_t * n, char *newname)
240{
241 Agraph_t *g;
242 IDTYPE new_id;
243
244 g = agroot(agraphof(n));
245 if (agfindnode_by_name(g, newname))
246 return FAILURE;
247 if (agmapnametoid(g, AGNODE, newname, &new_id, true)) {
248 if (agfindnode_by_id(agroot(g), new_id) == NULL) {
249 agfreeid(g, AGNODE, AGID(n));
250 agapply(g, (Agobj_t*)n, (agobjfn_t)dict_relabel, &new_id, false);
251 return SUCCESS;
252 } else {
253 agfreeid(g, AGNODE, new_id); /* couldn't use it after all */
254 }
255 /* obj* is unchanged, so no need to re agregister() */
256 }
257 return FAILURE;
258}
259
260/* lookup or insert <n> in <g> */
261Agnode_t *agsubnode(Agraph_t * g, Agnode_t * n0, int cflag)
262{
263 Agraph_t *par;
264 Agnode_t *n;
265
266 if (agroot(g) != n0->root)
267 return NULL;
268 n = agfindnode_by_id(g, AGID(n0));
269 if (n == NULL && cflag) {
270 if ((par = agparent(g))) {
271 n = agsubnode(par, n0, cflag);
272 installnode(g, n);
273 /* no callback for existing node insertion in subgraph (?) */
274 }
275 /* else impossible that <n> doesn't belong to <g> */
276 }
277 /* else lookup succeeded */
278 return n;
279}
280
286static bool agsubnodeideq(const Agsubnode_t *sn0, IDTYPE id) {
287 return AGID(sn0->node) == id;
288}
289
290static int agsubnodeseqcmpf(void *arg0, void *arg1) {
291 Agsubnode_t *sn0 = arg0;
292 Agsubnode_t *sn1 = arg1;
293
294 if (AGSEQ(sn0->node) < AGSEQ(sn1->node)) return -1;
295 if (AGSEQ(sn0->node) > AGSEQ(sn1->node)) return 1;
296 return 0;
297}
298
299/* free_subnode:
300 * Free Agsubnode_t allocated in installnode. This should
301 * only be done for subgraphs, as the root graph uses the
302 * subnode structure built into the node. This explains the
303 * AGSNMAIN test. Also, note that both the id and the seq
304 * dictionaries use the same subnode object, so only one
305 * should do the deletion.
306 */
307static void free_subnode(void *subnode) {
308 Agsubnode_t *sn = subnode;
309 if (!AGSNMAIN(sn))
310 agfree (sn->node->root, sn);
311}
312
314 .link = offsetof(Agsubnode_t, seq_link), // link offset
315 .freef = free_subnode,
316 .comparf = agsubnodeseqcmpf,
317};
318
319static void agnodesetfinger(Agraph_t * g, Agnode_t * n, void *ignored)
320{
321 Agsubnode_t template = {0};
322 template.node = n;
323 dtsearch(g->n_seq,&template);
324 (void)ignored;
325}
326
327static void agnoderenew(Agraph_t * g, Agnode_t * n, void *ignored)
328{
329 dtrenew(g->n_seq, dtfinger(g->n_seq));
330 (void)n;
331 (void)ignored;
332}
333
335{
336 Agraph_t *g;
337 Agnode_t *n, *nxt;
338
339
340 g = agroot(fst);
341 if (AGSEQ(fst) > AGSEQ(snd)) return SUCCESS;
342
343 /* move snd out of the way somewhere */
344 n = snd;
345 if (agapply (g,(Agobj_t *)n, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
346 return FAILURE;
347 }
348 {
349 uint64_t seq = g->clos->seq[AGNODE] + 2;
350 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
351 AGSEQ(snd) = seq & SEQ_MASK;
352 }
353 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnoderenew, n, false) != SUCCESS) {
354 return FAILURE;
355 }
356 n = agprvnode(g,snd);
357 do {
358 nxt = agprvnode(g,n);
359 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
360 return FAILURE;
361 }
362 uint64_t seq = AGSEQ(n) + 1;
363 assert((seq & SEQ_MASK) == seq && "sequence ID overflow");
364 AGSEQ(n) = seq & SEQ_MASK;
365 if (agapply(g, (Agobj_t *)n, (agobjfn_t)agnoderenew, n, false) != SUCCESS) {
366 return FAILURE;
367 }
368 if (n == fst) break;
369 n = nxt;
370 } while (n);
371 if (agapply(g, (Agobj_t *)snd, (agobjfn_t)agnodesetfinger, n, false) != SUCCESS) {
372 return FAILURE;
373 }
374 assert(AGSEQ(fst) != 0 && "sequence ID overflow");
375 AGSEQ(snd) = (AGSEQ(fst) - 1) & SEQ_MASK;
376 if (agapply(g, (Agobj_t *)snd, (agobjfn_t)agnoderenew, snd, false) != SUCCESS) {
377 return FAILURE;
378 }
379 return SUCCESS;
380}
381
387
389static Agsubnode_t *const TOMBSTONE = (Agsubnode_t *)-1;
390
391node_set_t *node_set_new(void) { return gv_alloc(sizeof(node_set_t)); }
392
406static size_t node_set_index(const node_set_t *self, IDTYPE id) {
407 assert(self != NULL);
408 assert(self->capacity != 0);
409 return (size_t)id % self->capacity;
410}
411
413 assert(self != NULL);
414 assert(item != NULL);
415
416 // a watermark ratio at which the set capacity should be expanded
417 static const double OCCUPANCY_THRESHOLD = 0.7; // 70%
418
419 // do we need to expand the backing store?
420 bool grow = false;
421
422 // we definitely do if it has no room at all
423 if (self->capacity == 0) {
424 grow = true;
425 }
426
427 // we might need to if it has exceeded the watermark
428 if (!grow && self->size / self->capacity > OCCUPANCY_THRESHOLD) {
429 grow = true;
430 }
431
432 if (grow) {
433 const size_t new_c = self->capacity == 0 ? 1024 : self->capacity * 2;
434 Agsubnode_t **new_slots = gv_calloc(new_c, sizeof(Agsubnode_t *));
435
436 // Construct a new set and copy everything into it. Note we need to rehash
437 // because capacity (and hence modulo wraparound behavior) has changed. This
438 // conveniently flushes out the tombstones too.
439 node_set_t new_self = {.slots = new_slots, .capacity = new_c};
440 for (size_t i = 0; i < self->capacity; ++i) {
441 // skip empty slots
442 if (self->slots[i] == NULL) {
443 continue;
444 }
445 // skip deleted slots
446 if (self->slots[i] == TOMBSTONE) {
447 continue;
448 }
449 node_set_add(&new_self, self->slots[i]);
450 }
451
452 // replace ourselves with this new set
453 free(self->slots);
454 *self = new_self;
455 }
456
457 assert(self->capacity > self->size);
458
459 const size_t index = node_set_index(self, item->node->base.tag.id);
460
461 for (size_t i = 0; i < self->capacity; ++i) {
462 const size_t candidate = (index + i) % self->capacity;
463
464 // if we found an empty slot or a previously deleted slot, we can insert
465 if (self->slots[candidate] == NULL || self->slots[candidate] == TOMBSTONE) {
466 self->slots[candidate] = item;
467 ++self->size;
468 return;
469 }
470 }
471
472 UNREACHABLE();
473}
474
476 assert(self != NULL);
477
478 // early exit to avoid `self->slots == NULL`/`self->capacity == 0`
479 // complications
480 if (self->size == 0) {
481 return NULL;
482 }
483
484 const size_t index = node_set_index(self, key);
485
486 for (size_t i = 0; i < self->capacity; ++i) {
487 const size_t candidate = (index + i) % self->capacity;
488
489 // if we found an empty slot, the sought item does not exist
490 if (self->slots[candidate] == NULL) {
491 return NULL;
492 }
493
494 // if we found a previously deleted slot, skip over it
495 if (self->slots[candidate] == TOMBSTONE) {
496 continue;
497 }
498
499 if (agsubnodeideq(self->slots[candidate], key)) {
500 return self->slots[candidate];
501 }
502 }
503
504 return NULL;
505}
506
508 assert(self != NULL);
509
510 // early exit to avoid `self->slots == NULL`/`self->capacity == 0`
511 // complications
512 if (self->size == 0) {
513 return;
514 }
515
516 const size_t index = node_set_index(self, item);
517
518 for (size_t i = 0; i < self->capacity; ++i) {
519 const size_t candidate = (index + i) % self->capacity;
520
521 // if we found an empty slot, the sought item does not exist
522 if (self->slots[candidate] == NULL) {
523 return;
524 }
525
526 // if we found a previously deleted slot, skip over it
527 if (self->slots[candidate] == TOMBSTONE) {
528 continue;
529 }
530
531 if (agsubnodeideq(self->slots[candidate], item)) {
532 assert(self->size > 0);
533 self->slots[candidate] = TOMBSTONE;
534 --self->size;
535 return;
536 }
537 }
538}
539
540size_t node_set_size(const node_set_t *self) {
541 assert(self != NULL);
542 return self->size;
543}
544
546 assert(self != NULL);
547
548 if (*self != NULL) {
549 free((*self)->slots);
550 }
551
552 free(*self);
553 *self = NULL;
554}
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
int agapply(Agraph_t *g, Agobj_t *obj, agobjfn_t fn, void *arg, int preorder)
Definition apply.c:60
char * AgDataRecName
Definition attr.c:171
#define dtsearch(d, o)
Definition cdt.h:191
CDT_API int dtsize(Dt_t *)
Definition dtsize.c:12
#define dtfinger(d)
Definition cdt.h:185
#define dtinsert(d, o)
Definition cdt.h:193
#define dtprev(d, o)
Definition cdt.h:190
CDT_API void * dtrenew(Dt_t *, void *)
Definition dtrenew.c:9
#define dtlast(d)
Definition cdt.h:189
#define dtdelete(d, o)
Definition cdt.h:194
#define dtnext(d, o)
Definition cdt.h:188
#define dtfirst(d)
Definition cdt.h:187
cgraph.h additions
void agfreeid(Agraph_t *g, int objtype, IDTYPE id)
Definition id.c:146
void agrecclose(Agobj_t *obj)
Definition rec.c:226
@ SEQ_MASK
Definition cghdr.h:77
int agmapnametoid(Agraph_t *g, int objtype, char *str, IDTYPE *result, bool createflag)
Definition id.c:112
void agdeledgeimage(Agraph_t *g, Agedge_t *edge, void *ignored)
Definition edge.c:304
int agallocid(Agraph_t *g, int objtype, IDTYPE request)
Definition id.c:141
uint64_t agnextseq(Agraph_t *g, int objtype)
Definition graph.c:153
#define FAILURE
Definition cghdr.h:44
void agregister(Agraph_t *g, int objtype, void *obj)
Definition id.c:185
#define SUCCESS
Definition cghdr.h:43
static Agnode_t * agfindnode_by_name(Agraph_t *g, char *name)
Definition node.c:30
static Agsubnode_t *const TOMBSTONE
a sentinel, marking a set slot from which an element has been deleted
Definition node.c:389
static int agsubnodeseqcmpf(void *arg0, void *arg1)
Definition node.c:290
Agsubnode_t * node_set_find(node_set_t *self, IDTYPE key)
Definition node.c:475
Agnode_t * agfindnode_by_id(Agraph_t *g, IDTYPE id)
Definition node.c:22
void agdelnodeimage(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:179
Dtdisc_t Ag_subnode_seq_disc
Definition node.c:313
static size_t node_set_index(const node_set_t *self, IDTYPE id)
Definition node.c:406
static void installnode(Agraph_t *g, Agnode_t *n)
Definition node.c:91
static void installnodetoroot(Agraph_t *g, Agnode_t *n)
Definition node.c:108
static void initnode(Agraph_t *g, Agnode_t *n)
Definition node.c:116
static void dict_relabel(Agraph_t *ignored, Agnode_t *n, void *arg)
Definition node.c:222
void node_set_free(node_set_t **self)
Definition node.c:545
static void agnoderenew(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:327
size_t node_set_size(const node_set_t *self)
Definition node.c:540
static bool agsubnodeideq(const Agsubnode_t *sn0, IDTYPE id)
Definition node.c:286
static Agnode_t * newnode(Agraph_t *g, IDTYPE id, uint64_t seq)
Definition node.c:72
node_set_t * node_set_new(void)
Definition node.c:391
void node_set_add(node_set_t *self, Agsubnode_t *item)
Definition node.c:412
void node_set_remove(node_set_t *self, IDTYPE item)
Definition node.c:507
static void free_subnode(void *subnode)
Definition node.c:307
static void agnodesetfinger(Agraph_t *g, Agnode_t *n, void *ignored)
Definition node.c:319
disc key
Definition exparse.y:214
void free(void *)
node NULL
Definition grammar.y:149
void * agalloc(Agraph_t *g, size_t size)
Definition mem.c:19
void agfree(Agraph_t *g, void *ptr)
Definition mem.c:49
void agnodeattr_init(Agraph_t *g, Agnode_t *n)
Definition attr.c:404
void agnodeattr_delete(Agnode_t *n)
Definition attr.c:413
void agmethod_delete(Agraph_t *g, void *obj)
Definition obj.c:137
void agmethod_init(Agraph_t *g, void *obj)
Definition obj.c:77
int agdeledge(Agraph_t *g, Agedge_t *arg_e)
Definition edge.c:334
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:93
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:84
#define AGSNMAIN(sn)
Definition cgraph.h:917
void(* agobjfn_t)(Agraph_t *g, Agobj_t *obj, void *arg)
Definition cgraph.h:369
Agnode_t * agnode(Agraph_t *g, char *name, int cflag)
Definition node.c:147
int agnodebefore(Agnode_t *fst, Agnode_t *snd)
Definition node.c:334
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agprvnode(Agraph_t *g, Agnode_t *n)
Definition node.c:62
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
Agnode_t * agsubnode(Agraph_t *g, Agnode_t *n0, int cflag)
Definition node.c:261
Agnode_t * agidnode(Agraph_t *g, IDTYPE id, int cflag)
Definition node.c:124
Agnode_t * aglstnode(Agraph_t *g)
Definition node.c:55
int agrelabel_node(Agnode_t *n, char *newname)
Definition node.c:239
int agdelnode(Agraph_t *g, Agnode_t *n)
removes a node from a graph or subgraph.
Definition node.c:197
Agsubnode_t * agsubrep(Agraph_t *g, Agnode_t *n)
Definition edge.c:144
Agraph_t * agraphof(void *obj)
Definition obj.c:184
#define AGID(obj)
returns the unique integer ID associated with the object
Definition cgraph.h:221
uint64_t IDTYPE
unique per main graph ID
Definition cgraph.h:73
#define AGTYPE(obj)
returns AGRAPH, AGNODE, or AGEDGE depending on the type of the object
Definition cgraph.h:216
Agraph_t * agroot(void *obj)
Definition obj.c:167
#define AGSEQ(obj)
Definition cgraph.h:225
@ AGNODE
Definition cgraph.h:207
void * agbindrec(void *obj, const char *name, unsigned int recsize, int move_to_front)
attaches a new record of the given size to the object
Definition rec.c:88
Agraph_t * agparent(Agraph_t *g)
Definition subg.c:90
static uint64_t id
Definition gv2gml.c:42
unordered set of Agsubnode_t *
string attribute container
Definition cgraph.h:631
uint64_t seq[3]
Definition cgraph.h:415
unsigned has_attrs
Definition cgraph.h:290
Agsubnode_t mainsub
Definition cgraph.h:262
Agraph_t * root
Definition cgraph.h:261
Agobj_t base
Definition cgraph.h:260
a generic header of Agraph_s, Agnode_s and Agedge_s
Definition cgraph.h:210
Agtag_t tag
access with AGTAG
Definition cgraph.h:211
graph or subgraph
Definition cgraph.h:425
struct graphviz_node_set * n_id
the node set indexed by ID
Definition cgraph.h:431
Dict_t * n_seq
the node set in sequence
Definition cgraph.h:430
Agclos_t * clos
shared resources
Definition cgraph.h:435
Agdesc_t desc
Definition cgraph.h:427
This is the node struct allocated per graph (or subgraph).
Definition cgraph.h:251
Agnode_t * node
Definition cgraph.h:254
IDTYPE id
Definition cgraph.h:203
int link
Definition cdt.h:91
size_t capacity
size of slots
Definition node.c:385
size_t size
number of elements in the set
Definition node.c:384
Agsubnode_t ** slots
backing store for elements
Definition node.c:383
Definition utils.c:748
#define UNREACHABLE()
Definition unreachable.h:30