Line data Source code
1 : /* Declare ultra high performance dynamic key-val maps of bounded
2 : compile time size. Typical usage:
3 :
4 : struct mymap {
5 : ulong key; // Technically "MAP_KEY_T MAP_KEY;" (default is ulong key)
6 : uint hash; // Technically "MAP_HASH_T MAP_HASH;" (default is uint hash), ==mymap_hash(key)
7 : ... key and hash can be located arbitrarily in struct
8 : ... hash is not required if MAP_MEMOIZE is zero
9 : ... the rest of the struct is POD state/values associated with key
10 : ... the mapping of a key to a map slot is arbitrary and might
11 : ... change over the lifetime of the key
12 : };
13 :
14 : typedef struct mymap mymap_t;
15 :
16 : #define MAP_NAME mymap
17 : #define MAP_T mymap_t
18 : #define MAP_LG_SLOT_CNT 12
19 : #include "util/tmpl/fd_map.c"
20 :
21 : will declare the following static inline APIs as a header only style
22 : library in the compilation unit:
23 :
24 : // align/footprint - Return the alignment/footprint required for a
25 : // memory region to be used as mymap.
26 : //
27 : // new - Format a memory region pointed to by shmem into a mymap.
28 : // Assumes shmem points to a region with the required alignment and
29 : // footprint not in use by anything else. Caller is not joined on
30 : // return. Returns shmem.
31 : //
32 : // join - Join a mymap. Assumes shmap points at a region formatted
33 : // as a mymap. Returns a handle of the callers join.
34 : //
35 : // leave - Leave a mymap. Assumes mymap points to a current join.
36 : // Returns a pointer to the shared memory region the join.
37 : //
38 : // delete - Unformat a memory region used as a mymap. Assumes
39 : // shmymap points to a formatted region with no current joins.
40 : // Returns a pointer to the unformatted memory region.
41 :
42 : ulong mymap_align ( void );
43 : ulong mymap_footprint( void );
44 : void * mymap_new ( void * shmem );
45 : mymap_t * mymap_join ( void * shmymap );
46 : void * mymap_leave ( mymap_t * mymap );
47 : void * mymap_delete ( void * shmymap );
48 :
49 : // Return the maximum number of keys that can be inserted into a
50 : // mymap. The mymap will become increasingly inefficient the more
51 : // keys there are. Recommend not using more than around half this
52 : // value.
53 :
54 : ulong mymap_key_max( void ); // == 2^MAP_LG_SLOT_CNT - 1
55 :
56 : // Return the number of slots in a mymap. This is to facilitate
57 : // iterating / listing all contents of a mymap (this process is not
58 : // algorithmically ideal for a sparse mymap). E.g.
59 : // mymap[slot_idx].key for slot_idx in [0,mymap_slot_cnt()] when
60 : // key!=0 is the set of all current key-vals in the mymap.
61 :
62 : ulong mymap_slot_cnt( void ); // == 2^MAP_LG_SLOT_CNT
63 :
64 : // Returns the index of the slot (allows communicating locations of
65 : // map entries between users of mymap in different address spaces).
66 : // Might imply a division (probably via magic multiply) if
67 : // sizeof(mymap_t) is not a power of two (as such, power-of-2
68 : // sizeof(mymap_t) have good Feng Shui). Assumes that mymap is a
69 : // current join and slot points to a slot in that join.
70 :
71 : ulong mymap_slot_idx( mymap_t const * mymap, mymap_t const * slot );
72 :
73 : // Returns the "null" key, which is the canonical key that will
74 : // never be inserted (typically zero).
75 :
76 : ulong mymap_key_null( void ); // == MAP_KEY_NULL
77 :
78 : // Return the 1/0 if key is a key that will never/might be inserted.
79 :
80 : int mymap_key_inval( ulong key );
81 :
82 : // Return the 1/0 if k0 and k1 are keys that are the same
83 :
84 : int mymap_key_equal( ulong k0, ulong k1 );
85 :
86 : // Return the hash of key used by the map. Should ideally be a
87 : // random mapping from MAP_KEY_T -> MAP_HASH_T.
88 :
89 : uint mymap_key_hash( ulong key );
90 :
91 : // Insert key into the map, fast O(1). Returns a pointer to the map
92 : // the entry with key on success and NULL on failure (i.e. key is
93 : // already in the map). The returned pointer lifetime is until
94 : // _any_ map remove or map leave. The caller should not change the
95 : // values in key or hash but is free to modify other fields in the
96 : // entry on return. Assumes map is a current join, there are less
97 : // than mymap_key_max() keys in the map and key is not a value that
98 : // will never be inserted.
99 :
100 : mymap_t * mymap_insert( mymap_t * map, ulong key );
101 :
102 : // Remove entry from map, fast O(1). Assumes map is a current join
103 : // and that entry points to a full entry currently in the map. When
104 : // the function returns, the entry pointed to by entry may be
105 : // clobbered.
106 : // Removal performance very slightly more optimal if sizeof(mymap_t)
107 : // is a power of two.
108 :
109 : void mymap_remove( mymap_t * map, mymap_t * entry );
110 :
111 : // Remove all entries from the map. O(map size).
112 :
113 : void mymap_clear( mymap_t * map );
114 :
115 : // Query map for key, fast O(1). Returns a pointer to the map slot
116 : // holding key or null if key not in map. The returned pointer
117 : // lifetime is until the next map remove or map leave. The caller
118 : // should not change key or hash but is free to modify other fields
119 : // in the entry. Assumes map is a current join and that key is
120 : // non-zero. mymap_query_const is a const correct version.
121 :
122 : mymap_t * mymap_query ( mymap_t * map, ulong key, mymap_t * null );
123 : mymap_t const * mymap_query_const( mymap_t const * map, ulong key, mymap_t const * null );
124 :
125 : You can do this as often as you like in a compilation unit to get
126 : different types of maps. Since it is all static inline, it is fine
127 : to do this in a header too. Further, options exist to use different
128 : hashing functions, variable length keys, etc as detailed below.
129 :
130 : For use with non-POD C++ structs, map_new assumes that a slot can be
131 : initialized to empty by assigning its key to MAP_KEY_NULL. Map insert
132 : will use MAP_KEY_MOVE to move the user provided key into the slot key
133 : value. Map remove will MAP_MOVE slots as necessary to preserve their
134 : probe sequences and assumes the user has already cleaned up the entry
135 : to remove enough so that all that needs to be done to eliminate the
136 : entry from the map is assign the entry's key to MAP_KEY_NULL.
137 :
138 : mymap_t * slot = mymap_insert( map, cxx_key );
139 : if( FD_UNLIKELY( !slot ) ) ... handle failure (cxx_key was not moved)
140 : else {
141 : ... mymap_insert did a MAP_KEY_MOVE of cxx_key into slot->key
142 : ... clean cxx_key's shell here as necessary here
143 : ... initialize other slot fields as necessary
144 : }
145 :
146 : and on removal:
147 :
148 : ... clean up other slot fields as necessary
149 : ... clean up slot->key as necessary
150 : mymap_remove( map, slot );
151 : ... the mapping of keys to map slots might have been changed by the
152 : ... mymap_remove. Any motion of slots was done via:
153 : ... MAP_MOVE( dst_slot, src_slot )
154 :
155 : fd_map align and footprint are declaration friendly. E.g.
156 :
157 : mymap_t map[ ... map slot cnt ... ];
158 :
159 : will have the appropriate alignment and footprint for a mymap_t map. */
160 :
161 : #include "../bits/fd_bits.h"
162 :
163 : #ifndef MAP_NAME
164 : #error "Define MAP_NAME"
165 : #endif
166 :
167 : /* A MAP_T should be something reasonable to shallow copy with the
168 : fields described above. */
169 :
170 : #ifndef MAP_T
171 : #error "Define MAP_T struct"
172 : #endif
173 :
174 : /* MAP_HASH_T should be an unsigned integral type. */
175 :
176 : #ifndef MAP_HASH_T
177 1845481656 : #define MAP_HASH_T uint
178 : #endif
179 :
180 : /* MAP_HASH is the MAP_T hash field name. Defaults to hash. */
181 :
182 : #ifndef MAP_HASH
183 6906 : #define MAP_HASH hash
184 : #endif
185 :
186 : /* MAP_LG_SLOT_CNT is the log2 of the number of slots in the map. */
187 :
188 : #ifndef MAP_LG_SLOT_CNT
189 : #error "Define MAP_LG_SLOT_CNT, should be at least 1 and much less than 63"
190 : #endif
191 :
192 : /* MAP_KEY_T should be something reasonable to pass to a static inline
193 : by value, assign to MAP_KEY_NULL, compare for equality and copy.
194 : E.g. a uint, ulong, __m128i, etc. */
195 :
196 : #ifndef MAP_KEY_T
197 433553730 : #define MAP_KEY_T ulong
198 : #else
199 : #if !defined(MAP_KEY_NULL) || !defined(MAP_KEY_INVAL) || !defined(MAP_KEY_EQUAL) || !defined(MAP_KEY_EQUAL_IS_SLOW) || !defined(MAP_KEY_HASH)
200 : #error "Define MAP_KEY_NULL, MAP_KEY_INVAL, MAP_KEY_EQUAL, MAP_KEY_EQUAL_IS_SLOW, and MAP_KEY_HASH if using a custom MAP_KEY_T"
201 : #endif
202 : #endif
203 :
204 : /* MAP_KEY is the MAP_T key field name. Defaults to key. */
205 :
206 : #ifndef MAP_KEY
207 5060627703 : #define MAP_KEY key
208 : #endif
209 :
210 : #ifndef MAP_KEY_NULL
211 : #if defined(MAP_KEY_INVAL)
212 : #error "MAP_KEY_INVAL is defined but MAP_KEY_NULL is not"
213 : #endif
214 : #endif
215 :
216 : #ifndef MAP_KEY_INVAL
217 : #if defined(MAP_KEY_NULL)
218 : #error "MAP_KEY_NULL is defined but MAP_KEY_INVAL is not"
219 : #endif
220 : #endif
221 :
222 : /* MAP_KEY_NULL is a key that will never be inserted. */
223 :
224 : #ifndef MAP_KEY_NULL
225 530850 : #define MAP_KEY_NULL 0UL
226 : #endif
227 :
228 : /* MAP_KEY_INVAL returns 1 if k0 is key that will never be inserted
229 : and zero otherwise. Note that MAP_KEY_INVAL( MAP_KEY_NULL ) should
230 : be true. This should be generally fast. */
231 :
232 : #ifndef MAP_KEY_INVAL
233 433545702 : #define MAP_KEY_INVAL(k) !(k)
234 : #endif
235 :
236 : /* MAP_KEY_EQUAL returns 0/1 if k0 is the same/different. Note that
237 : this function may also be called with MAP_KEY_NULL. */
238 :
239 : #ifndef MAP_KEY_EQUAL
240 470285517 : #define MAP_KEY_EQUAL(k0,k1) (k0)==(k1)
241 : #endif
242 :
243 : /* If MAP_KEY_EQUAL_IS_SLOW is slow (e.g. variable length string
244 : compare, large buffer compares, etc), set MAP_KEY_EQUAL_IS_SLOW to
245 : non-zero. Then, if MAP_MEMOIZE (below) is set, precomputed key hashes
246 : will be used accelerate key insert and key query. */
247 :
248 : #ifndef MAP_KEY_EQUAL_IS_SLOW
249 : #define MAP_KEY_EQUAL_IS_SLOW 0
250 : #endif
251 :
252 : /* MAP_KEY_HASH takes a key and maps it into MAP_HASH_T uniform pseudo
253 : randomly. */
254 :
255 : #ifndef MAP_KEY_HASH
256 121091733 : #define MAP_KEY_HASH(key) (MAP_HASH_T)fd_ulong_hash( key )
257 : #endif
258 :
259 : /* MAP_KEY_MOVE moves the contents from src to dst. Non-POD key types
260 : need to customize this accordingly (and handle the case of
261 : ks==MAP_KEY_NULL). Defaults to shallow copy. */
262 :
263 : #ifndef MAP_KEY_MOVE
264 1603471896 : #define MAP_KEY_MOVE(kd,ks) (kd)=(ks)
265 : #endif
266 :
267 : /* MAP_MOVE moves the contents of a MAP_T from src to dst. Non-POD key
268 : types need to customize this accordingly. Defaults to shallow copy. */
269 :
270 : #ifndef MAP_MOVE
271 374472 : #define MAP_MOVE(d,s) (d)=(s)
272 : #endif
273 :
274 : /* If MAP_MEMOIZE is defined to non-zero, the MAP_T requires a hash
275 : field that will hold the value of the MAP_KEY_HASH of the MAP_T's key
276 : field when the map slot is not empty (undefined otherwise). This is
277 : useful for accelerating user operations that might need a hash of the
278 : key and for accelerating remove operations. It is also potentially
279 : useful as a way to accelerate slow key comparison operations (see
280 : MAP_KEY_EQUAL_IS_SLOW). */
281 :
282 : #ifndef MAP_MEMOIZE
283 : #define MAP_MEMOIZE 1
284 : #endif
285 :
286 : /* MAP_QUERY_OPT allows the user to specify how the map query function
287 : should be optimized.
288 : 0 -> optimize for low fill ratio
289 : 1 -> optimize for low fill ratio and extremely rare query failure
290 : 2 -> optimize for low fill ratio and extremely rare query success */
291 :
292 : #ifndef MAP_QUERY_OPT
293 : #define MAP_QUERY_OPT 0
294 : #endif
295 :
296 : #if FD_TMPL_USE_HANDHOLDING
297 : #include "../log/fd_log.h"
298 : #endif
299 :
300 : /* Implementation *****************************************************/
301 :
302 10039320411 : #define MAP_(n) FD_EXPAND_THEN_CONCAT3(MAP_NAME,_,n)
303 4014772635 : #define MAP_SLOT_CNT (1UL<<(MAP_LG_SLOT_CNT))
304 4013489988 : #define MAP_SLOT_MASK (MAP_SLOT_CNT-1UL)
305 :
306 : FD_PROTOTYPES_BEGIN
307 :
308 : /* Private APIs *******************************************************/
309 :
310 : /* Get the linear probing starting slot for a key and the slot to probe
311 : after a given slot */
312 :
313 1845491580 : FD_FN_CONST static inline ulong MAP_(private_start)( MAP_HASH_T hash ) { return (ulong)(hash & (MAP_HASH_T)MAP_SLOT_MASK); }
314 2167997406 : FD_FN_CONST static inline ulong MAP_(private_next) ( ulong slot ) { return (++slot) & MAP_SLOT_MASK; }
315 :
316 : /* Public APIS ********************************************************/
317 :
318 7716 : FD_FN_CONST static inline ulong MAP_(align) ( void ) { return alignof(MAP_T); }
319 8493 : FD_FN_CONST static inline ulong MAP_(footprint)( void ) { return sizeof(MAP_T)*MAP_SLOT_CNT; }
320 :
321 : static inline void *
322 8553 : MAP_(new)( void * shmem ) {
323 : # if FD_TMPL_USE_HANDHOLDING
324 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, MAP_(align)() ) ) ) FD_LOG_CRIT(( "unaligned shmem" ));
325 : # endif
326 8553 : MAP_T * map = (MAP_T *)shmem;
327 1269129 : for( ulong slot_idx=0UL; slot_idx<MAP_SLOT_CNT; slot_idx++ ) map[ slot_idx ].MAP_KEY = (MAP_KEY_NULL);
328 8553 : return map;
329 8553 : }
330 :
331 69459217 : static inline MAP_T * MAP_(join) ( void * shmap ) { return (MAP_T *)shmap; }
332 69459166 : static inline void * MAP_(leave) ( MAP_T * map ) { return map; }
333 7686 : static inline void * MAP_(delete)( void * shmap ) { return shmap; }
334 :
335 1002 : FD_FN_CONST static inline ulong MAP_(key_max) ( void ) { return MAP_SLOT_MASK; }
336 1542 : FD_FN_CONST static inline ulong MAP_(slot_cnt)( void ) { return MAP_SLOT_CNT; }
337 :
338 : FD_FN_CONST static inline ulong
339 1603467471 : MAP_(slot_idx)( MAP_T const * map, MAP_T const * entry ) {
340 : # if FD_TMPL_USE_HANDHOLDING
341 : if( FD_UNLIKELY( ((ulong)(entry-map)>=MAP_SLOT_CNT) | (map>entry) ) ) FD_LOG_CRIT(( "index out of bounds" ));
342 : # endif
343 1603467471 : return (ulong)(entry-map);
344 1603467471 : }
345 :
346 1551 : FD_FN_CONST static inline MAP_KEY_T MAP_(key_null)( void ) { return (MAP_KEY_NULL); }
347 :
348 : /* These are FD_FN_PURE instead of FD_FN_CONST in case a non-POD
349 : MAP_KEY_T. FIXME: CONSIDER LETTING THE COMPILER SORT THIS OUT? */
350 :
351 5493587073 : FD_FN_PURE static inline int MAP_(key_inval)( MAP_KEY_T k0 ) { return (MAP_KEY_INVAL(k0)); }
352 598769574 : FD_FN_PURE static inline int MAP_(key_equal)( MAP_KEY_T k0, MAP_KEY_T k1 ) { return (MAP_KEY_EQUAL(k0,k1)); }
353 :
354 1845491580 : FD_FN_PURE static inline MAP_HASH_T MAP_(key_hash)( MAP_KEY_T key ) { return (MAP_KEY_HASH(key)); }
355 :
356 : FD_FN_UNUSED static MAP_T * /* Work around -Winline */
357 : MAP_(insert)( MAP_T * map,
358 1603722738 : MAP_KEY_T key ) {
359 : # if FD_TMPL_USE_HANDHOLDING
360 : if( FD_UNLIKELY( MAP_(key_inval)( key ) ) ) FD_LOG_CRIT(( "invalid key" ));
361 : # endif
362 1603722738 : MAP_HASH_T hash = MAP_(key_hash)( key );
363 1603722738 : ulong slot = MAP_(private_start)( hash );
364 1603722738 : MAP_T * m;
365 1737524886 : for(;;) {
366 1737524886 : m = map + slot;
367 1737524886 : MAP_KEY_T map_key = m->MAP_KEY;
368 1737524886 : if( FD_LIKELY( MAP_(key_inval)( map_key ) ) ) break; /* Optimize for not found */
369 : # if MAP_MEMOIZE && MAP_KEY_EQUAL_IS_SLOW /* ... and then for searching */
370 1296 : if( FD_UNLIKELY( m->MAP_HASH==hash && MAP_(key_equal)( map_key, key ) ) ) return NULL;
371 : # else
372 134051694 : if( FD_UNLIKELY( MAP_(key_equal)( map_key, key ) ) ) return NULL;
373 133800855 : # endif
374 133802148 : slot = MAP_(private_next)( slot );
375 133802148 : }
376 1603471896 : MAP_KEY_MOVE( m->MAP_KEY, key );
377 : # if MAP_MEMOIZE
378 5712 : m->MAP_HASH = hash;
379 : # endif
380 1603471896 : return m;
381 1603722738 : }
382 :
383 : static inline void
384 : MAP_(remove)( MAP_T * map,
385 1603465935 : MAP_T * entry ) {
386 1603465935 : ulong slot = MAP_(slot_idx)( map, entry );
387 :
388 1603840407 : for(;;) {
389 :
390 : /* Make a hole at slot */
391 :
392 1603840407 : map[slot].MAP_KEY = (MAP_KEY_NULL);
393 1603840407 : ulong hole = slot;
394 :
395 : /* The creation of a hole at slot might have disrupted the probe
396 : sequence involving the keys in any contiguously occupied map
397 : entry after slot. */
398 :
399 1727328339 : for(;;) {
400 1727328339 : slot = MAP_(private_next)( slot );
401 :
402 : /* At this point, map entries (hole,slot) (cyclic) are occupied
403 : and the probe sequence for these has been confirmed to be
404 : intact. If slot is empty, then all probe sequences are intact. */
405 :
406 1727328339 : MAP_KEY_T key = map[slot].MAP_KEY;
407 1727328339 : if( MAP_(key_inval)(key) ) return;
408 :
409 : /* slot is occupied. If a probe looking for the key at slot does
410 : not start its scan in (hole,slot] (cyclic), its scan will fail
411 : erroneously due to the hole just made. In this case, we move
412 : slot to hole to restore the probe sequence for the key at slot
413 : and then make a new hole at slot. As the new hole could break
414 : the other probe sequences, we start over on the new hole. */
415 :
416 : # if MAP_MEMOIZE
417 0 : MAP_HASH_T hash = map[slot].MAP_HASH;
418 : # else
419 123862404 : MAP_HASH_T hash = MAP_(key_hash)( key );
420 : # endif
421 123862404 : ulong start = MAP_(private_start)( hash );
422 123862404 : if( !(((hole<start) & (start<=slot)) | ((hole>slot) & ((hole<start) | (start<=slot)))) ) break;
423 123862404 : }
424 :
425 374472 : MAP_MOVE( map[hole], map[slot] );
426 374472 : }
427 : /* never get here */
428 1603465935 : }
429 :
430 : static inline void
431 27 : MAP_(clear)( MAP_T * map ) {
432 3483 : for( ulong slot_idx=0UL; slot_idx<MAP_SLOT_CNT; slot_idx++ ) map[ slot_idx ].MAP_KEY = (MAP_KEY_NULL);
433 27 : }
434 :
435 : FD_FN_PURE FD_FN_UNUSED static MAP_T * /* Work around -Winline */
436 : MAP_(query)( MAP_T * map,
437 : MAP_KEY_T key,
438 117906438 : MAP_T * null ) {
439 : # if FD_TMPL_USE_HANDHOLDING
440 : if( FD_UNLIKELY( MAP_(key_inval)( key ) ) ) FD_LOG_CRIT(( "invalid key" ));
441 : # endif
442 117906438 : MAP_HASH_T hash = MAP_(key_hash)( key );
443 117906438 : ulong slot = MAP_(private_start)( hash );
444 117906438 : MAP_T * m;
445 424773357 : for(;;) {
446 424773357 : m = map + slot;
447 424773357 : MAP_KEY_T map_key = m->MAP_KEY;
448 :
449 : # if MAP_MEMOIZE && MAP_KEY_EQUAL_IS_SLOW
450 2706 : # define MAP_IMPL_QUERY_FOUND (hash==m->MAP_HASH && MAP_(key_equal)( map_key, key ))
451 : # else
452 424770387 : # define MAP_IMPL_QUERY_FOUND MAP_(key_equal)( map_key, key )
453 : # endif
454 :
455 : # if MAP_QUERY_OPT==0
456 424773093 : int found = MAP_IMPL_QUERY_FOUND;
457 424773093 : int empty = MAP_(key_inval)( map_key );
458 : int done = found | empty;
459 424773093 : if( empty ) m = null; /* cmov */
460 424773093 : FD_COMPILER_FORGET( done );
461 424773093 : if( FD_LIKELY( done ) ) break;
462 : # elif MAP_QUERY_OPT==1
463 0 : if( FD_LIKELY( MAP_IMPL_QUERY_FOUND ) ) break;
464 0 : if( FD_LIKELY( MAP_(key_inval)( map_key ) ) ) return null;
465 : # else
466 264 : if( FD_LIKELY( MAP_(key_inval)( map_key ) ) ) return null;
467 243 : if( FD_LIKELY( MAP_IMPL_QUERY_FOUND ) ) break;
468 204 : # endif
469 :
470 204 : # undef MAP_IMPL_QUERY_FOUND
471 :
472 306866919 : slot = MAP_(private_next)( slot );
473 306866919 : }
474 39 : return m;
475 117906438 : }
476 :
477 : FD_FN_PURE static inline MAP_T const *
478 : MAP_(query_const)( MAP_T const * map,
479 : MAP_KEY_T key,
480 231 : MAP_T const * null ) {
481 231 : return (MAP_T const *)MAP_(query)( (MAP_T *)map, key, (MAP_T *)null ); /* query doesn't actual change any memory */
482 231 : }
483 :
484 : FD_PROTOTYPES_END
485 :
486 : #undef MAP_SLOT_MASK
487 : #undef MAP_SLOT_CNT
488 : #undef MAP_
489 :
490 : /* End implementation *************************************************/
491 :
492 : #undef MAP_QUERY_OPT
493 : #undef MAP_MEMOIZE
494 : #undef MAP_MOVE
495 : #undef MAP_KEY_MOVE
496 : #undef MAP_KEY_HASH
497 : #undef MAP_KEY_EQUAL_IS_SLOW
498 : #undef MAP_KEY_EQUAL
499 : #undef MAP_KEY_INVAL
500 : #undef MAP_KEY_NULL
501 : #undef MAP_KEY
502 : #undef MAP_KEY_T
503 : #undef MAP_LG_SLOT_CNT
504 : #undef MAP_HASH
505 : #undef MAP_HASH_T
506 : #undef MAP_T
507 : #undef MAP_NAME
508 :
|