xref: /plan9/sys/src/cmd/gs/src/iname.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989, 1995, 1997, 1998, 1999 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: iname.c,v 1.7 2003/09/03 03:22:59 giles Exp $ */
18 /* Name lookup for Ghostscript interpreter */
19 #include "memory_.h"
20 #include "string_.h"
21 #include "ghost.h"
22 #include "gsstruct.h"
23 #include "gxobj.h"		/* for o_set_unmarked */
24 #include "ierrors.h"
25 #include "inamedef.h"
26 #include "imemory.h"		/* for isave.h */
27 #include "isave.h"
28 #include "store.h"
29 
30 /* Public values */
31 const uint name_max_string = max_name_string;
32 
33 /* Define the permutation table for name hashing. */
34 private const byte hash_permutation[256] = {
35     NAME_HASH_PERMUTATION_DATA
36 };
37 
38 /* Define the data for the 1-character names. */
39 private const byte nt_1char_names[NT_1CHAR_SIZE] = {
40     NT_1CHAR_NAMES_DATA
41 };
42 
43 /* Structure descriptors */
44 gs_private_st_simple(st_name_sub_table, name_sub_table, "name_sub_table");
45 gs_private_st_composite(st_name_string_sub_table, name_string_sub_table_t,
46 			"name_string_sub_table_t",
47 			name_string_sub_enum_ptrs, name_string_sub_reloc_ptrs);
48 gs_private_st_composite(st_name_table, name_table, "name_table",
49 			name_table_enum_ptrs, name_table_reloc_ptrs);
50 
51 /* Forward references */
52 private int name_alloc_sub(name_table *);
53 private void name_free_sub(name_table *, uint);
54 private void name_scan_sub(name_table *, uint, bool);
55 
56 /* Debugging printout */
57 #ifdef DEBUG
58 private void
name_print(const char * msg,const name_table * nt,uint nidx,const int * pflag)59 name_print(const char *msg, const name_table *nt, uint nidx, const int *pflag)
60 {
61     const name_string_t *pnstr = names_index_string_inline(nt, nidx);
62     const name *pname = names_index_ptr_inline(nt, nidx);
63     const byte *str = pnstr->string_bytes;
64 
65     dlprintf1("[n]%s", msg);
66     if (pflag)
67 	dprintf1("(%d)", *pflag);
68     dprintf2(" (0x%lx#%u)", (ulong)pname, nidx);
69     debug_print_string(str, pnstr->string_size);
70     dprintf2("(0x%lx,%u)\n", (ulong)str, pnstr->string_size);
71 }
72 #  define if_debug_name(msg, nt, nidx, pflag)\
73      if ( gs_debug_c('n') ) name_print(msg, nt, nidx, pflag)
74 #else
75 #  define if_debug_name(msg, nt, nidx, pflag) DO_NOTHING
76 #endif
77 
78 /* Initialize a name table */
79 name_table *
names_init(ulong count,gs_ref_memory_t * imem)80 names_init(ulong count, gs_ref_memory_t *imem)
81 {
82     gs_memory_t *mem = (gs_memory_t *)imem;
83     name_table *nt;
84     int i;
85 
86     if (count == 0)
87 	count = max_name_count + 1L;
88     else if (count - 1 > max_name_count)
89 	return 0;
90     nt = gs_alloc_struct(mem, name_table, &st_name_table, "name_init(nt)");
91     if (nt == 0)
92 	return 0;
93     memset(nt, 0, sizeof(name_table));
94     nt->max_sub_count =
95 	((count - 1) | nt_sub_index_mask) >> nt_log2_sub_size;
96     nt->name_string_attrs = imemory_space(imem) | a_readonly;
97     nt->memory = mem;
98     /* Initialize the one-character names. */
99     /* Start by creating the necessary sub-tables. */
100     for (i = 0; i < NT_1CHAR_FIRST + NT_1CHAR_SIZE; i += nt_sub_size) {
101 	int code = name_alloc_sub(nt);
102 
103 	if (code < 0) {
104 	    while (nt->sub_next > 0)
105 		name_free_sub(nt, --(nt->sub_next));
106 	    gs_free_object(mem, nt, "name_init(nt)");
107 	    return 0;
108 	}
109     }
110     for (i = -1; i < NT_1CHAR_SIZE; i++) {
111 	uint ncnt = NT_1CHAR_FIRST + i;
112 	uint nidx = name_count_to_index(ncnt);
113 	name *pname = names_index_ptr_inline(nt, nidx);
114 	name_string_t *pnstr = names_index_string_inline(nt, nidx);
115 
116 	if (i < 0)
117 	    pnstr->string_bytes = nt_1char_names,
118 		pnstr->string_size = 0;
119 	else
120 	    pnstr->string_bytes = nt_1char_names + i,
121 		pnstr->string_size = 1;
122 	pnstr->foreign_string = 1;
123 	pnstr->mark = 1;
124 	pname->pvalue = pv_no_defn;
125     }
126     nt->perm_count = NT_1CHAR_FIRST + NT_1CHAR_SIZE;
127     /* Reconstruct the free list. */
128     nt->free = 0;
129     names_trace_finish(nt, NULL);
130     return nt;
131 }
132 
133 /* Get the allocator for the name table. */
134 gs_memory_t *
names_memory(const name_table * nt)135 names_memory(const name_table * nt)
136 {
137     return nt->memory;
138 }
139 
140 /* Look up or enter a name in the table. */
141 /* Return 0 or an error code. */
142 /* The return may overlap the characters of the string! */
143 /* See iname.h for the meaning of enterflag. */
144 int
names_ref(name_table * nt,const byte * ptr,uint size,ref * pref,int enterflag)145 names_ref(name_table *nt, const byte *ptr, uint size, ref *pref, int enterflag)
146 {
147     name *pname;
148     name_string_t *pnstr;
149     uint nidx;
150     uint *phash;
151 
152     /* Compute a hash for the string. */
153     /* Make a special check for 1-character names. */
154     switch (size) {
155     case 0:
156 	nidx = name_count_to_index(1);
157 	pname = names_index_ptr_inline(nt, nidx);
158 	goto mkn;
159     case 1:
160 	if (*ptr < NT_1CHAR_SIZE) {
161 	    uint hash = *ptr + NT_1CHAR_FIRST;
162 
163 	    nidx = name_count_to_index(hash);
164 	    pname = names_index_ptr_inline(nt, nidx);
165 	    goto mkn;
166 	}
167 	/* falls through */
168     default: {
169 	uint hash;
170 
171 	NAME_HASH(hash, hash_permutation, ptr, size);
172 	phash = nt->hash + (hash & (NT_HASH_SIZE - 1));
173     }
174     }
175 
176     for (nidx = *phash; nidx != 0;
177 	 nidx = name_next_index(nidx, pnstr)
178 	) {
179 	pnstr = names_index_string_inline(nt, nidx);
180 	if (pnstr->string_size == size &&
181 	    !memcmp_inline(ptr, pnstr->string_bytes, size)
182 	    ) {
183 	    pname = name_index_ptr_inline(nt, nidx);
184 	    goto mkn;
185 	}
186     }
187     /* Name was not in the table.  Make a new entry. */
188     if (enterflag < 0)
189 	return_error(e_undefined);
190     if (size > max_name_string)
191 	return_error(e_limitcheck);
192     nidx = nt->free;
193     if (nidx == 0) {
194 	int code = name_alloc_sub(nt);
195 
196 	if (code < 0)
197 	    return code;
198 	nidx = nt->free;
199     }
200     pnstr = names_index_string_inline(nt, nidx);
201     if (enterflag == 1) {
202 	byte *cptr = (byte *)gs_alloc_string(nt->memory, size,
203 					     "names_ref(string)");
204 
205 	if (cptr == 0)
206 	    return_error(e_VMerror);
207 	memcpy(cptr, ptr, size);
208 	pnstr->string_bytes = cptr;
209 	pnstr->foreign_string = 0;
210     } else {
211 	pnstr->string_bytes = ptr;
212 	pnstr->foreign_string = (enterflag == 0 ? 1 : 0);
213     }
214     pnstr->string_size = size;
215     pname = name_index_ptr_inline(nt, nidx);
216     pname->pvalue = pv_no_defn;
217     nt->free = name_next_index(nidx, pnstr);
218     set_name_next_index(nidx, pnstr, *phash);
219     *phash = nidx;
220     if_debug_name("new name", nt, nidx, &enterflag);
221  mkn:
222     make_name(pref, nidx, pname);
223     return 0;
224 }
225 
226 /* Get the string for a name. */
227 void
names_string_ref(const name_table * nt,const ref * pnref,ref * psref)228 names_string_ref(const name_table * nt, const ref * pnref /* t_name */ ,
229 		 ref * psref /* result, t_string */ )
230 {
231     const name_string_t *pnstr = names_string_inline(nt, pnref);
232 
233     make_const_string(psref,
234 		      (pnstr->foreign_string ? avm_foreign | a_readonly :
235 		       nt->name_string_attrs),
236 		      pnstr->string_size,
237 		      (const byte *)pnstr->string_bytes);
238 }
239 
240 /* Convert a t_string object to a name. */
241 /* Copy the executable attribute. */
242 int
names_from_string(name_table * nt,const ref * psref,ref * pnref)243 names_from_string(name_table * nt, const ref * psref, ref * pnref)
244 {
245     int exec = r_has_attr(psref, a_executable);
246     int code = names_ref(nt, psref->value.bytes, r_size(psref), pnref, 1);
247 
248     if (code < 0)
249 	return code;
250     if (exec)
251 	r_set_attrs(pnref, a_executable);
252     return code;
253 }
254 
255 /* Enter a (permanently allocated) C string as a name. */
256 int
names_enter_string(name_table * nt,const char * str,ref * pref)257 names_enter_string(name_table * nt, const char *str, ref * pref)
258 {
259     return names_ref(nt, (const byte *)str, strlen(str), pref, 0);
260 }
261 
262 /* Invalidate the value cache for a name. */
263 void
names_invalidate_value_cache(name_table * nt,const ref * pnref)264 names_invalidate_value_cache(name_table * nt, const ref * pnref)
265 {
266     pnref->value.pname->pvalue = pv_other;
267 }
268 
269 /* Convert between names and indices. */
270 #undef names_index
271 name_index_t
names_index(const name_table * nt,const ref * pnref)272 names_index(const name_table * nt, const ref * pnref)
273 {
274     return names_index_inline(nt, pnref);
275 }
276 void
names_index_ref(const name_table * nt,name_index_t index,ref * pnref)277 names_index_ref(const name_table * nt, name_index_t index, ref * pnref)
278 {
279     names_index_ref_inline(nt, index, pnref);
280 }
281 name *
names_index_ptr(const name_table * nt,name_index_t index)282 names_index_ptr(const name_table * nt, name_index_t index)
283 {
284     return names_index_ptr_inline(nt, index);
285 }
286 
287 /* Get the index of the next valid name. */
288 /* The argument is 0 or a valid index. */
289 /* Return 0 if there are no more. */
290 name_index_t
names_next_valid_index(name_table * nt,name_index_t nidx)291 names_next_valid_index(name_table * nt, name_index_t nidx)
292 {
293     const name_string_sub_table_t *ssub =
294 	nt->sub[nidx >> nt_log2_sub_size].strings;
295     const name_string_t *pnstr;
296 
297     do {
298 	++nidx;
299 	if ((nidx & nt_sub_index_mask) == 0)
300 	    for (;; nidx += nt_sub_size) {
301 		if ((nidx >> nt_log2_sub_size) >= nt->sub_count)
302 		    return 0;
303 		ssub = nt->sub[nidx >> nt_log2_sub_size].strings;
304 		if (ssub != 0)
305 		    break;
306 	    }
307 	pnstr = &ssub->strings[nidx & nt_sub_index_mask];
308     }
309     while (pnstr->string_bytes == 0);
310     return nidx;
311 }
312 
313 /* ------ Garbage collection ------ */
314 
315 /* Unmark all non-permanent names before a garbage collection. */
316 void
names_unmark_all(name_table * nt)317 names_unmark_all(name_table * nt)
318 {
319     uint si;
320     name_string_sub_table_t *ssub;
321 
322     for (si = 0; si < nt->sub_count; ++si)
323 	if ((ssub = nt->sub[si].strings) != 0) {
324 	    uint i;
325 
326 	    /* We can make the test much more efficient if we want.... */
327 	    for (i = 0; i < nt_sub_size; ++i)
328 		if (name_index_to_count((si << nt_log2_sub_size) + i) >=
329 		    nt->perm_count)
330 		    ssub->strings[i].mark = 0;
331 	}
332 }
333 
334 /* Mark a name.  Return true if new mark.  We export this so we can mark */
335 /* character names in the character cache. */
336 bool
names_mark_index(name_table * nt,name_index_t nidx)337 names_mark_index(name_table * nt, name_index_t nidx)
338 {
339     name_string_t *pnstr = names_index_string_inline(nt, nidx);
340 
341     if (pnstr->mark)
342 	return false;
343     pnstr->mark = 1;
344     return true;
345 }
346 
347 /* Get the object (sub-table) containing a name. */
348 /* The garbage collector needs this so it can relocate pointers to names. */
349 void /*obj_header_t */ *
names_ref_sub_table(name_table * nt,const ref * pnref)350 names_ref_sub_table(name_table * nt, const ref * pnref)
351 {
352     /* When this procedure is called, the pointers from the name table */
353     /* to the sub-tables may or may not have been relocated already, */
354     /* so we can't use them.  Instead, we have to work backwards from */
355     /* the name pointer itself. */
356     return pnref->value.pname - (r_size(pnref) & nt_sub_index_mask);
357 }
358 void /*obj_header_t */ *
names_index_sub_table(name_table * nt,name_index_t index)359 names_index_sub_table(name_table * nt, name_index_t index)
360 {
361     return nt->sub[index >> nt_log2_sub_size].names;
362 }
363 void /*obj_header_t */ *
names_index_string_sub_table(name_table * nt,name_index_t index)364 names_index_string_sub_table(name_table * nt, name_index_t index)
365 {
366     return nt->sub[index >> nt_log2_sub_size].strings;
367 }
368 
369 /*
370  * Clean up the name table after the trace/mark phase of a garbage
371  * collection, by removing names that aren't marked.  gcst == NULL indicates
372  * we're doing this for initialization or restore rather than for a GC.
373  */
374 void
names_trace_finish(name_table * nt,gc_state_t * gcst)375 names_trace_finish(name_table * nt, gc_state_t * gcst)
376 {
377     uint *phash = &nt->hash[0];
378     uint i;
379 
380     for (i = 0; i < NT_HASH_SIZE; phash++, i++) {
381 	name_index_t prev = 0;
382 	/*
383 	 * The following initialization is only to pacify compilers:
384 	 * pnprev is only referenced if prev has been set in the loop,
385 	 * in which case pnprev is also set.
386 	 */
387 	name_string_t *pnprev = 0;
388 	name_index_t nidx = *phash;
389 
390 	while (nidx != 0) {
391 	    name_string_t *pnstr = names_index_string_inline(nt, nidx);
392 	    name_index_t next = name_next_index(nidx, pnstr);
393 
394 	    if (pnstr->mark) {
395 		prev = nidx;
396 		pnprev = pnstr;
397 	    } else {
398 		if_debug_name("GC remove name", nt, nidx, NULL);
399 		/* Zero out the string data for the GC. */
400 		pnstr->string_bytes = 0;
401 		pnstr->string_size = 0;
402 		if (prev == 0)
403 		    *phash = next;
404 		else
405 		    set_name_next_index(prev, pnprev, next);
406 	    }
407 	    nidx = next;
408 	}
409     }
410     /* Reconstruct the free list. */
411     nt->free = 0;
412     for (i = nt->sub_count; i--;) {
413 	name_sub_table *sub = nt->sub[i].names;
414 	name_string_sub_table_t *ssub = nt->sub[i].strings;
415 
416 	if (sub != 0) {
417 	    name_scan_sub(nt, i, true);
418 	    if (nt->sub[i].names == 0 && gcst != 0) {
419 		/* Mark the just-freed sub-table as unmarked. */
420 		o_set_unmarked((obj_header_t *)sub - 1);
421 		o_set_unmarked((obj_header_t *)ssub - 1);
422 	    }
423 	}
424 	if (i == 0)
425 	    break;
426     }
427     nt->sub_next = 0;
428 }
429 
430 /* ------ Save/restore ------ */
431 
432 /* Clean up the name table before a restore. */
433 /* Currently, this is never called, because the name table is allocated */
434 /* in system VM.  However, for a Level 1 system, we might choose to */
435 /* allocate the name table in global VM; in this case, this routine */
436 /* would be called before doing the global part of a top-level restore. */
437 /* Currently we don't make any attempt to optimize this. */
438 void
names_restore(name_table * nt,alloc_save_t * save)439 names_restore(name_table * nt, alloc_save_t * save)
440 {
441     /* We simply mark all names older than the save, */
442     /* and let names_trace_finish sort everything out. */
443     uint si;
444 
445     for (si = 0; si < nt->sub_count; ++si)
446 	if (nt->sub[si].strings != 0) {
447 	    uint i;
448 
449 	    for (i = 0; i < nt_sub_size; ++i) {
450 		name_string_t *pnstr =
451 		    names_index_string_inline(nt, (si << nt_log2_sub_size) + i);
452 
453 		if (pnstr->string_bytes == 0)
454 		    pnstr->mark = 0;
455 		else if (pnstr->foreign_string) {
456 		    /* Avoid storing into a read-only name string. */
457 		    if (!pnstr->mark)
458 			pnstr->mark = 1;
459 		} else
460 		    pnstr->mark =
461 			!alloc_is_since_save(pnstr->string_bytes, save);
462 	    }
463 	}
464     names_trace_finish(nt, NULL);
465 }
466 
467 /* ------ Internal procedures ------ */
468 
469 /* Allocate the next sub-table. */
470 private int
name_alloc_sub(name_table * nt)471 name_alloc_sub(name_table * nt)
472 {
473     gs_memory_t *mem = nt->memory;
474     uint sub_index = nt->sub_next;
475     name_sub_table *sub;
476     name_string_sub_table_t *ssub;
477 
478     for (;; ++sub_index) {
479 	if (sub_index > nt->max_sub_count)
480 	    return_error(e_limitcheck);
481 	if (nt->sub[sub_index].names == 0)
482 	    break;
483     }
484     nt->sub_next = sub_index + 1;
485     if (nt->sub_next > nt->sub_count)
486 	nt->sub_count = nt->sub_next;
487     sub = gs_alloc_struct(mem, name_sub_table, &st_name_sub_table,
488 			  "name_alloc_sub(sub-table)");
489     ssub = gs_alloc_struct(mem, name_string_sub_table_t,
490 			   &st_name_string_sub_table,
491 			  "name_alloc_sub(string sub-table)");
492     if (sub == 0 || ssub == 0) {
493 	gs_free_object(mem, ssub, "name_alloc_sub(string sub-table)");
494 	gs_free_object(mem, sub, "name_alloc_sub(sub-table)");
495 	return_error(e_VMerror);
496     }
497     memset(sub, 0, sizeof(name_sub_table));
498     memset(ssub, 0, sizeof(name_string_sub_table_t));
499     /* The following code is only used if EXTEND_NAMES is non-zero. */
500 #if name_extension_bits > 0
501     sub->high_index = (sub_index >> (16 - nt_log2_sub_size)) << 16;
502 #endif
503     nt->sub[sub_index].names = sub;
504     nt->sub[sub_index].strings = ssub;
505     /* Add the newly allocated entries to the free list. */
506     /* Note that the free list will only be properly sorted if */
507     /* it was empty initially. */
508     name_scan_sub(nt, sub_index, false);
509 #ifdef DEBUG
510     if (gs_debug_c('n')) {	/* Print the lengths of the hash chains. */
511 	int i0;
512 
513 	for (i0 = 0; i0 < NT_HASH_SIZE; i0 += 16) {
514 	    int i;
515 
516 	    dlprintf1("[n]chain %d:", i0);
517 	    for (i = i0; i < i0 + 16; i++) {
518 		int n = 0;
519 		uint nidx;
520 
521 		for (nidx = nt->hash[i]; nidx != 0;
522 		     nidx = name_next_index(nidx,
523 					    names_index_string_inline(nt, nidx))
524 		    )
525 		    n++;
526 		dprintf1(" %d", n);
527 	    }
528 	    dputc('\n');
529 	}
530     }
531 #endif
532     return 0;
533 }
534 
535 /* Free a sub-table. */
536 private void
name_free_sub(name_table * nt,uint sub_index)537 name_free_sub(name_table * nt, uint sub_index)
538 {
539     gs_free_object(nt->memory, nt->sub[sub_index].strings,
540 		   "name_free_sub(string sub-table)");
541     gs_free_object(nt->memory, nt->sub[sub_index].names,
542 		   "name_free_sub(sub-table)");
543     nt->sub[sub_index].names = 0;
544     nt->sub[sub_index].strings = 0;
545 }
546 
547 /* Scan a sub-table and add unmarked entries to the free list. */
548 /* We add the entries in decreasing count order, so the free list */
549 /* will stay sorted.  If all entries are unmarked and free_empty is true, */
550 /* free the sub-table. */
551 private void
name_scan_sub(name_table * nt,uint sub_index,bool free_empty)552 name_scan_sub(name_table * nt, uint sub_index, bool free_empty)
553 {
554     name_string_sub_table_t *ssub = nt->sub[sub_index].strings;
555     uint free = nt->free;
556     uint nbase = sub_index << nt_log2_sub_size;
557     uint ncnt = nbase + (nt_sub_size - 1);
558     bool keep = !free_empty;
559 
560     if (ssub == 0)
561 	return;
562     if (nbase == 0)
563 	nbase = 1, keep = true;	/* don't free name 0 */
564     for (;; --ncnt) {
565 	uint nidx = name_count_to_index(ncnt);
566 	name_string_t *pnstr = &ssub->strings[nidx & nt_sub_index_mask];
567 
568 	if (pnstr->mark)
569 	    keep = true;
570 	else {
571 	    set_name_next_index(nidx, pnstr, free);
572 	    free = nidx;
573 	}
574 	if (ncnt == nbase)
575 	    break;
576     }
577     if (keep)
578 	nt->free = free;
579     else {
580 	/* No marked entries, free the sub-table. */
581 	name_free_sub(nt, sub_index);
582 	if (sub_index == nt->sub_count - 1) {
583 	    /* Back up over a final run of deleted sub-tables. */
584 	    do {
585 		--sub_index;
586 	    } while (nt->sub[sub_index].names == 0);
587 	    nt->sub_count = sub_index + 1;
588 	    if (nt->sub_next > sub_index)
589 		nt->sub_next = sub_index;
590 	} else if (nt->sub_next == sub_index)
591 	    nt->sub_next--;
592     }
593 }
594 
595 /* Garbage collector enumeration and relocation procedures. */
596 private
ENUM_PTRS_BEGIN_PROC(name_table_enum_ptrs)597 ENUM_PTRS_BEGIN_PROC(name_table_enum_ptrs)
598 {
599     EV_CONST name_table *const nt = vptr;
600     uint i = index >> 1;
601 
602     if (i >= nt->sub_count)
603 	return 0;
604     if (index & 1)
605 	ENUM_RETURN(nt->sub[i].strings);
606     else
607 	ENUM_RETURN(nt->sub[i].names);
608 }
609 ENUM_PTRS_END_PROC
RELOC_PTRS_WITH(name_table_reloc_ptrs,name_table * nt)610 private RELOC_PTRS_WITH(name_table_reloc_ptrs, name_table *nt)
611 {
612     uint sub_count = nt->sub_count;
613     uint i;
614 
615     /* Now we can relocate the sub-table pointers. */
616     for (i = 0; i < sub_count; i++) {
617 	RELOC_VAR(nt->sub[i].names);
618 	RELOC_VAR(nt->sub[i].strings);
619     }
620     /*
621      * We also need to relocate the cached value pointers.
622      * We don't do this here, but in a separate scan over the
623      * permanent dictionaries, at the very end of garbage collection.
624      */
625 }
626 RELOC_PTRS_END
627 
ENUM_PTRS_BEGIN_PROC(name_string_sub_enum_ptrs)628 private ENUM_PTRS_BEGIN_PROC(name_string_sub_enum_ptrs)
629 {
630     return 0;
631 }
632 ENUM_PTRS_END_PROC
RELOC_PTRS_BEGIN(name_string_sub_reloc_ptrs)633 private RELOC_PTRS_BEGIN(name_string_sub_reloc_ptrs)
634 {
635     name_string_t *pnstr = ((name_string_sub_table_t *)vptr)->strings;
636     uint i;
637 
638     for (i = 0; i < nt_sub_size; ++pnstr, ++i) {
639 	if (pnstr->string_bytes != 0 && !pnstr->foreign_string) {
640 	    gs_const_string nstr;
641 
642 	    nstr.data = pnstr->string_bytes;
643 	    nstr.size = pnstr->string_size;
644 	    RELOC_CONST_STRING_VAR(nstr);
645 	    pnstr->string_bytes = nstr.data;
646 	}
647     }
648 }
649 RELOC_PTRS_END
650