1848b8605Smrg/* glxhash.c -- Small hash table support for integer -> integer mapping
2848b8605Smrg * Taken from libdrm.
3848b8605Smrg *
4848b8605Smrg * Created: Sun Apr 18 09:35:45 1999 by faith@precisioninsight.com
5848b8605Smrg *
6848b8605Smrg * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
7848b8605Smrg * All Rights Reserved.
8848b8605Smrg *
9848b8605Smrg * Permission is hereby granted, free of charge, to any person obtaining a
10848b8605Smrg * copy of this software and associated documentation files (the "Software"),
11848b8605Smrg * to deal in the Software without restriction, including without limitation
12848b8605Smrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13848b8605Smrg * and/or sell copies of the Software, and to permit persons to whom the
14848b8605Smrg * Software is furnished to do so, subject to the following conditions:
15848b8605Smrg *
16848b8605Smrg * The above copyright notice and this permission notice (including the next
17848b8605Smrg * paragraph) shall be included in all copies or substantial portions of the
18848b8605Smrg * Software.
19848b8605Smrg *
20848b8605Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21848b8605Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22848b8605Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23848b8605Smrg * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24848b8605Smrg * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25848b8605Smrg * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26848b8605Smrg * DEALINGS IN THE SOFTWARE.
27848b8605Smrg *
28848b8605Smrg * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
29848b8605Smrg *
30848b8605Smrg * DESCRIPTION
31848b8605Smrg *
32848b8605Smrg * This file contains a straightforward implementation of a fixed-sized
33848b8605Smrg * hash table using self-organizing linked lists [Knuth73, pp. 398-399] for
34848b8605Smrg * collision resolution.  There are two potentially interesting things
35848b8605Smrg * about this implementation:
36848b8605Smrg *
37848b8605Smrg * 1) The table is power-of-two sized.  Prime sized tables are more
38848b8605Smrg * traditional, but do not have a significant advantage over power-of-two
39848b8605Smrg * sized table, especially when double hashing is not used for collision
40848b8605Smrg * resolution.
41848b8605Smrg *
42848b8605Smrg * 2) The hash computation uses a table of random integers [Hanson97,
43848b8605Smrg * pp. 39-41].
44848b8605Smrg *
45848b8605Smrg * FUTURE ENHANCEMENTS
46848b8605Smrg *
47848b8605Smrg * With a table size of 512, the current implementation is sufficient for a
48848b8605Smrg * few hundred keys.  Since this is well above the expected size of the
49848b8605Smrg * tables for which this implementation was designed, the implementation of
50848b8605Smrg * dynamic hash tables was postponed until the need arises.  A common (and
51848b8605Smrg * naive) approach to dynamic hash table implementation simply creates a
52848b8605Smrg * new hash table when necessary, rehashes all the data into the new table,
53848b8605Smrg * and destroys the old table.  The approach in [Larson88] is superior in
54848b8605Smrg * two ways: 1) only a portion of the table is expanded when needed,
55848b8605Smrg * distributing the expansion cost over several insertions, and 2) portions
56848b8605Smrg * of the table can be locked, enabling a scalable thread-safe
57848b8605Smrg * implementation.
58848b8605Smrg *
59848b8605Smrg * REFERENCES
60848b8605Smrg *
61848b8605Smrg * [Hanson97] David R. Hanson.  C Interfaces and Implementations:
62848b8605Smrg * Techniques for Creating Reusable Software.  Reading, Massachusetts:
63848b8605Smrg * Addison-Wesley, 1997.
64848b8605Smrg *
65848b8605Smrg * [Knuth73] Donald E. Knuth. The Art of Computer Programming.  Volume 3:
66848b8605Smrg * Sorting and Searching.  Reading, Massachusetts: Addison-Wesley, 1973.
67848b8605Smrg *
68848b8605Smrg * [Larson88] Per-Ake Larson. "Dynamic Hash Tables".  CACM 31(4), April
69848b8605Smrg * 1988, pp. 446-457.
70848b8605Smrg *
71848b8605Smrg */
72848b8605Smrg
73848b8605Smrg#include "glxhash.h"
74848b8605Smrg#include <X11/Xfuncproto.h>
75848b8605Smrg
76848b8605Smrg#define HASH_MAIN 0
77848b8605Smrg
78848b8605Smrg#include <stdio.h>
79848b8605Smrg#include <stdlib.h>
80848b8605Smrg#include <string.h>
81848b8605Smrg
82848b8605Smrg#define HASH_MAGIC 0xdeadbeef
83848b8605Smrg#define HASH_DEBUG 0
84848b8605Smrg#define HASH_SIZE  512          /* Good for about 100 entries */
85848b8605Smrg                                /* If you change this value, you probably
86848b8605Smrg                                   have to change the HashHash hashing
87848b8605Smrg                                   function! */
88848b8605Smrg
89848b8605Smrg#define HASH_ALLOC malloc
90848b8605Smrg#define HASH_FREE  free
91848b8605Smrg#ifndef __GLIBC__
92848b8605Smrg#define HASH_RANDOM_DECL	char *ps, rs[256]
93848b8605Smrg#define HASH_RANDOM_INIT(seed)	ps = initstate(seed, rs, sizeof(rs))
94848b8605Smrg#define HASH_RANDOM		random()
95848b8605Smrg#define HASH_RANDOM_DESTROY	setstate(ps)
96848b8605Smrg#else
97848b8605Smrg#define HASH_RANDOM_DECL	struct random_data rd; int32_t rv; char rs[256]
98848b8605Smrg#define HASH_RANDOM_INIT(seed)					\
99848b8605Smrg   do {								\
100848b8605Smrg      (void) memset(&rd, 0, sizeof(rd));			\
101848b8605Smrg      (void) initstate_r(seed, rs, sizeof(rs), &rd);		\
102848b8605Smrg   } while(0)
103848b8605Smrg#define HASH_RANDOM             ((void) random_r(&rd, &rv), rv)
104848b8605Smrg#define HASH_RANDOM_DESTROY
105848b8605Smrg#endif
106848b8605Smrg
107848b8605Smrgtypedef struct __glxHashBucket
108848b8605Smrg{
109848b8605Smrg   unsigned long key;
110848b8605Smrg   void *value;
111848b8605Smrg   struct __glxHashBucket *next;
112848b8605Smrg} __glxHashBucket, *__glxHashBucketPtr;
113848b8605Smrg
114848b8605Smrgtypedef struct __glxHashTable *__glxHashTablePtr;
115848b8605Smrgstruct __glxHashTable
116848b8605Smrg{
117848b8605Smrg   unsigned long magic;
118848b8605Smrg   unsigned long hits;          /* At top of linked list */
119848b8605Smrg   unsigned long partials;      /* Not at top of linked list */
120848b8605Smrg   unsigned long misses;        /* Not in table */
121848b8605Smrg   __glxHashBucketPtr buckets[HASH_SIZE];
122848b8605Smrg   int p0;
123848b8605Smrg   __glxHashBucketPtr p1;
124848b8605Smrg};
125848b8605Smrg
126848b8605Smrgstatic unsigned long
127848b8605SmrgHashHash(unsigned long key)
128848b8605Smrg{
129848b8605Smrg   unsigned long hash = 0;
130848b8605Smrg   unsigned long tmp = key;
131848b8605Smrg   static int init = 0;
132848b8605Smrg   static unsigned long scatter[256];
133848b8605Smrg   int i;
134848b8605Smrg
135848b8605Smrg   if (!init) {
136848b8605Smrg      HASH_RANDOM_DECL;
137848b8605Smrg      HASH_RANDOM_INIT(37);
138848b8605Smrg      for (i = 0; i < 256; i++)
139848b8605Smrg         scatter[i] = HASH_RANDOM;
140848b8605Smrg      HASH_RANDOM_DESTROY;
141848b8605Smrg      ++init;
142848b8605Smrg   }
143848b8605Smrg
144848b8605Smrg   while (tmp) {
145848b8605Smrg      hash = (hash << 1) + scatter[tmp & 0xff];
146848b8605Smrg      tmp >>= 8;
147848b8605Smrg   }
148848b8605Smrg
149848b8605Smrg   hash %= HASH_SIZE;
150848b8605Smrg#if HASH_DEBUG
151848b8605Smrg   printf("Hash(%d) = %d\n", key, hash);
152848b8605Smrg#endif
153848b8605Smrg   return hash;
154848b8605Smrg}
155848b8605Smrg
156848b8605Smrg_X_HIDDEN __glxHashTable *
157848b8605Smrg__glxHashCreate(void)
158848b8605Smrg{
159848b8605Smrg   __glxHashTablePtr table;
160848b8605Smrg   int i;
161848b8605Smrg
162848b8605Smrg   table = HASH_ALLOC(sizeof(*table));
163848b8605Smrg   if (!table)
164848b8605Smrg      return NULL;
165848b8605Smrg   table->magic = HASH_MAGIC;
166848b8605Smrg   table->hits = 0;
167848b8605Smrg   table->partials = 0;
168848b8605Smrg   table->misses = 0;
169848b8605Smrg
170848b8605Smrg   for (i = 0; i < HASH_SIZE; i++)
171848b8605Smrg      table->buckets[i] = NULL;
172848b8605Smrg   return table;
173848b8605Smrg}
174848b8605Smrg
175848b8605Smrg_X_HIDDEN int
176848b8605Smrg__glxHashDestroy(__glxHashTable * t)
177848b8605Smrg{
178848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
179848b8605Smrg   __glxHashBucketPtr bucket;
180848b8605Smrg   __glxHashBucketPtr next;
181848b8605Smrg   int i;
182848b8605Smrg
183848b8605Smrg   if (table->magic != HASH_MAGIC)
184848b8605Smrg      return -1;                /* Bad magic */
185848b8605Smrg
186848b8605Smrg   for (i = 0; i < HASH_SIZE; i++) {
187848b8605Smrg      for (bucket = table->buckets[i]; bucket;) {
188848b8605Smrg         next = bucket->next;
189848b8605Smrg         HASH_FREE(bucket);
190848b8605Smrg         bucket = next;
191848b8605Smrg      }
192848b8605Smrg   }
193848b8605Smrg   HASH_FREE(table);
194848b8605Smrg   return 0;
195848b8605Smrg}
196848b8605Smrg
197848b8605Smrg/* Find the bucket and organize the list so that this bucket is at the
198848b8605Smrg   top. */
199848b8605Smrg
200848b8605Smrgstatic __glxHashBucketPtr
201848b8605SmrgHashFind(__glxHashTablePtr table, unsigned long key, unsigned long *h)
202848b8605Smrg{
203848b8605Smrg   unsigned long hash = HashHash(key);
204848b8605Smrg   __glxHashBucketPtr prev = NULL;
205848b8605Smrg   __glxHashBucketPtr bucket;
206848b8605Smrg
207848b8605Smrg   if (h)
208848b8605Smrg      *h = hash;
209848b8605Smrg
210848b8605Smrg   for (bucket = table->buckets[hash]; bucket; bucket = bucket->next) {
211848b8605Smrg      if (bucket->key == key) {
212848b8605Smrg         if (prev) {
213848b8605Smrg            /* Organize */
214848b8605Smrg            prev->next = bucket->next;
215848b8605Smrg            bucket->next = table->buckets[hash];
216848b8605Smrg            table->buckets[hash] = bucket;
217848b8605Smrg            ++table->partials;
218848b8605Smrg         }
219848b8605Smrg         else {
220848b8605Smrg            ++table->hits;
221848b8605Smrg         }
222848b8605Smrg         return bucket;
223848b8605Smrg      }
224848b8605Smrg      prev = bucket;
225848b8605Smrg   }
226848b8605Smrg   ++table->misses;
227848b8605Smrg   return NULL;
228848b8605Smrg}
229848b8605Smrg
230848b8605Smrg_X_HIDDEN int
231848b8605Smrg__glxHashLookup(__glxHashTable * t, unsigned long key, void **value)
232848b8605Smrg{
233848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
234848b8605Smrg   __glxHashBucketPtr bucket;
235848b8605Smrg
236848b8605Smrg   if (!table || table->magic != HASH_MAGIC)
237848b8605Smrg      return -1;                /* Bad magic */
238848b8605Smrg
239848b8605Smrg   bucket = HashFind(table, key, NULL);
240848b8605Smrg   if (!bucket)
241848b8605Smrg      return 1;                 /* Not found */
242848b8605Smrg   *value = bucket->value;
243848b8605Smrg   return 0;                    /* Found */
244848b8605Smrg}
245848b8605Smrg
246848b8605Smrg_X_HIDDEN int
247848b8605Smrg__glxHashInsert(__glxHashTable * t, unsigned long key, void *value)
248848b8605Smrg{
249848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
250848b8605Smrg   __glxHashBucketPtr bucket;
251848b8605Smrg   unsigned long hash;
252848b8605Smrg
253848b8605Smrg   if (table->magic != HASH_MAGIC)
254848b8605Smrg      return -1;                /* Bad magic */
255848b8605Smrg
256848b8605Smrg   if (HashFind(table, key, &hash))
257848b8605Smrg      return 1;                 /* Already in table */
258848b8605Smrg
259848b8605Smrg   bucket = HASH_ALLOC(sizeof(*bucket));
260848b8605Smrg   if (!bucket)
261848b8605Smrg      return -1;                /* Error */
262848b8605Smrg   bucket->key = key;
263848b8605Smrg   bucket->value = value;
264848b8605Smrg   bucket->next = table->buckets[hash];
265848b8605Smrg   table->buckets[hash] = bucket;
266848b8605Smrg#if HASH_DEBUG
267848b8605Smrg   printf("Inserted %d at %d/%p\n", key, hash, bucket);
268848b8605Smrg#endif
269848b8605Smrg   return 0;                    /* Added to table */
270848b8605Smrg}
271848b8605Smrg
272848b8605Smrg_X_HIDDEN int
273848b8605Smrg__glxHashDelete(__glxHashTable * t, unsigned long key)
274848b8605Smrg{
275848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
276848b8605Smrg   unsigned long hash;
277848b8605Smrg   __glxHashBucketPtr bucket;
278848b8605Smrg
279848b8605Smrg   if (table->magic != HASH_MAGIC)
280848b8605Smrg      return -1;                /* Bad magic */
281848b8605Smrg
282848b8605Smrg   bucket = HashFind(table, key, &hash);
283848b8605Smrg
284848b8605Smrg   if (!bucket)
285848b8605Smrg      return 1;                 /* Not found */
286848b8605Smrg
287848b8605Smrg   table->buckets[hash] = bucket->next;
288848b8605Smrg   HASH_FREE(bucket);
289848b8605Smrg   return 0;
290848b8605Smrg}
291848b8605Smrg
292848b8605Smrg_X_HIDDEN int
293848b8605Smrg__glxHashNext(__glxHashTable * t, unsigned long *key, void **value)
294848b8605Smrg{
295848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
296848b8605Smrg
297848b8605Smrg   while (table->p0 < HASH_SIZE) {
298848b8605Smrg      if (table->p1) {
299848b8605Smrg         *key = table->p1->key;
300848b8605Smrg         *value = table->p1->value;
301848b8605Smrg         table->p1 = table->p1->next;
302848b8605Smrg         return 1;
303848b8605Smrg      }
304848b8605Smrg      table->p1 = table->buckets[table->p0];
305848b8605Smrg      ++table->p0;
306848b8605Smrg   }
307848b8605Smrg   return 0;
308848b8605Smrg}
309848b8605Smrg
310848b8605Smrg_X_HIDDEN int
311848b8605Smrg__glxHashFirst(__glxHashTable * t, unsigned long *key, void **value)
312848b8605Smrg{
313848b8605Smrg   __glxHashTablePtr table = (__glxHashTablePtr) t;
314848b8605Smrg
315848b8605Smrg   if (table->magic != HASH_MAGIC)
316848b8605Smrg      return -1;                /* Bad magic */
317848b8605Smrg
318848b8605Smrg   table->p0 = 0;
319848b8605Smrg   table->p1 = table->buckets[0];
320848b8605Smrg   return __glxHashNext(table, key, value);
321848b8605Smrg}
322848b8605Smrg
323848b8605Smrg#if HASH_MAIN
324848b8605Smrg#define DIST_LIMIT 10
325848b8605Smrgstatic int dist[DIST_LIMIT];
326848b8605Smrg
327848b8605Smrgstatic void
328848b8605Smrgclear_dist(void)
329848b8605Smrg{
330848b8605Smrg   int i;
331848b8605Smrg
332848b8605Smrg   for (i = 0; i < DIST_LIMIT; i++)
333848b8605Smrg      dist[i] = 0;
334848b8605Smrg}
335848b8605Smrg
336848b8605Smrgstatic int
337848b8605Smrgcount_entries(__glxHashBucketPtr bucket)
338848b8605Smrg{
339848b8605Smrg   int count = 0;
340848b8605Smrg
341848b8605Smrg   for (; bucket; bucket = bucket->next)
342848b8605Smrg      ++count;
343848b8605Smrg   return count;
344848b8605Smrg}
345848b8605Smrg
346848b8605Smrgstatic void
347848b8605Smrgupdate_dist(int count)
348848b8605Smrg{
349848b8605Smrg   if (count >= DIST_LIMIT)
350848b8605Smrg      ++dist[DIST_LIMIT - 1];
351848b8605Smrg   else
352848b8605Smrg      ++dist[count];
353848b8605Smrg}
354848b8605Smrg
355848b8605Smrgstatic void
356848b8605Smrgcompute_dist(__glxHashTablePtr table)
357848b8605Smrg{
358848b8605Smrg   int i;
359848b8605Smrg   __glxHashBucketPtr bucket;
360848b8605Smrg
361848b8605Smrg   printf("Hits = %ld, partials = %ld, misses = %ld\n",
362848b8605Smrg          table->hits, table->partials, table->misses);
363848b8605Smrg   clear_dist();
364848b8605Smrg   for (i = 0; i < HASH_SIZE; i++) {
365848b8605Smrg      bucket = table->buckets[i];
366848b8605Smrg      update_dist(count_entries(bucket));
367848b8605Smrg   }
368848b8605Smrg   for (i = 0; i < DIST_LIMIT; i++) {
369848b8605Smrg      if (i != DIST_LIMIT - 1)
370848b8605Smrg         printf("%5d %10d\n", i, dist[i]);
371848b8605Smrg      else
372848b8605Smrg         printf("other %10d\n", dist[i]);
373848b8605Smrg   }
374848b8605Smrg}
375848b8605Smrg
376848b8605Smrgstatic void
377848b8605Smrgcheck_table(__glxHashTablePtr table, unsigned long key, unsigned long value)
378848b8605Smrg{
379848b8605Smrg   unsigned long retval = 0;
380848b8605Smrg   int retcode = __glxHashLookup(table, key, &retval);
381848b8605Smrg
382848b8605Smrg   switch (retcode) {
383848b8605Smrg   case -1:
384848b8605Smrg      printf("Bad magic = 0x%08lx:"
385848b8605Smrg             " key = %lu, expected = %lu, returned = %lu\n",
386848b8605Smrg             table->magic, key, value, retval);
387848b8605Smrg      break;
388848b8605Smrg   case 1:
389848b8605Smrg      printf("Not found: key = %lu, expected = %lu returned = %lu\n",
390848b8605Smrg             key, value, retval);
391848b8605Smrg      break;
392848b8605Smrg   case 0:
393848b8605Smrg      if (value != retval)
394848b8605Smrg         printf("Bad value: key = %lu, expected = %lu, returned = %lu\n",
395848b8605Smrg                key, value, retval);
396848b8605Smrg      break;
397848b8605Smrg   default:
398848b8605Smrg      printf("Bad retcode = %d: key = %lu, expected = %lu, returned = %lu\n",
399848b8605Smrg             retcode, key, value, retval);
400848b8605Smrg      break;
401848b8605Smrg   }
402848b8605Smrg}
403848b8605Smrg
404848b8605Smrgint
405848b8605Smrgmain(void)
406848b8605Smrg{
407848b8605Smrg   __glxHashTablePtr table;
408848b8605Smrg   int i;
409848b8605Smrg
410848b8605Smrg   printf("\n***** 256 consecutive integers ****\n");
411848b8605Smrg   table = __glxHashCreate();
412848b8605Smrg   for (i = 0; i < 256; i++)
413848b8605Smrg      __glxHashInsert(table, i, i);
414848b8605Smrg   for (i = 0; i < 256; i++)
415848b8605Smrg      check_table(table, i, i);
416848b8605Smrg   for (i = 256; i >= 0; i--)
417848b8605Smrg      check_table(table, i, i);
418848b8605Smrg   compute_dist(table);
419848b8605Smrg   __glxHashDestroy(table);
420848b8605Smrg
421848b8605Smrg   printf("\n***** 1024 consecutive integers ****\n");
422848b8605Smrg   table = __glxHashCreate();
423848b8605Smrg   for (i = 0; i < 1024; i++)
424848b8605Smrg      __glxHashInsert(table, i, i);
425848b8605Smrg   for (i = 0; i < 1024; i++)
426848b8605Smrg      check_table(table, i, i);
427848b8605Smrg   for (i = 1024; i >= 0; i--)
428848b8605Smrg      check_table(table, i, i);
429848b8605Smrg   compute_dist(table);
430848b8605Smrg   __glxHashDestroy(table);
431848b8605Smrg
432848b8605Smrg   printf("\n***** 1024 consecutive page addresses (4k pages) ****\n");
433848b8605Smrg   table = __glxHashCreate();
434848b8605Smrg   for (i = 0; i < 1024; i++)
435848b8605Smrg      __glxHashInsert(table, i * 4096, i);
436848b8605Smrg   for (i = 0; i < 1024; i++)
437848b8605Smrg      check_table(table, i * 4096, i);
438848b8605Smrg   for (i = 1024; i >= 0; i--)
439848b8605Smrg      check_table(table, i * 4096, i);
440848b8605Smrg   compute_dist(table);
441848b8605Smrg   __glxHashDestroy(table);
442848b8605Smrg
443848b8605Smrg   printf("\n***** 1024 random integers ****\n");
444848b8605Smrg   table = __glxHashCreate();
445848b8605Smrg   srandom(0xbeefbeef);
446848b8605Smrg   for (i = 0; i < 1024; i++)
447848b8605Smrg      __glxHashInsert(table, random(), i);
448848b8605Smrg   srandom(0xbeefbeef);
449848b8605Smrg   for (i = 0; i < 1024; i++)
450848b8605Smrg      check_table(table, random(), i);
451848b8605Smrg   srandom(0xbeefbeef);
452848b8605Smrg   for (i = 0; i < 1024; i++)
453848b8605Smrg      check_table(table, random(), i);
454848b8605Smrg   compute_dist(table);
455848b8605Smrg   __glxHashDestroy(table);
456848b8605Smrg
457848b8605Smrg   printf("\n***** 5000 random integers ****\n");
458848b8605Smrg   table = __glxHashCreate();
459848b8605Smrg   srandom(0xbeefbeef);
460848b8605Smrg   for (i = 0; i < 5000; i++)
461848b8605Smrg      __glxHashInsert(table, random(), i);
462848b8605Smrg   srandom(0xbeefbeef);
463848b8605Smrg   for (i = 0; i < 5000; i++)
464848b8605Smrg      check_table(table, random(), i);
465848b8605Smrg   srandom(0xbeefbeef);
466848b8605Smrg   for (i = 0; i < 5000; i++)
467848b8605Smrg      check_table(table, random(), i);
468848b8605Smrg   compute_dist(table);
469848b8605Smrg   __glxHashDestroy(table);
470848b8605Smrg
471848b8605Smrg   return 0;
472848b8605Smrg}
473848b8605Smrg#endif
474