/* Copyright (C) 1989, 1995, 1997, 1998, 1999 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: iname.c,v 1.3.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */ /* Name lookup for Ghostscript interpreter */ #include "memory_.h" #include "string_.h" #include "ghost.h" #include "gsstruct.h" #include "gxobj.h" /* for o_set_unmarked */ #include "errors.h" #include "inamedef.h" #include "imemory.h" /* for isave.h */ #include "isave.h" #include "store.h" /* Public values */ const uint name_max_string = max_name_string; /* Define the permutation table for name hashing. */ private const byte hash_permutation[256] = { NAME_HASH_PERMUTATION_DATA }; /* Define the data for the 1-character names. */ private const byte nt_1char_names[NT_1CHAR_SIZE] = { NT_1CHAR_NAMES_DATA }; /* Structure descriptors */ gs_private_st_simple(st_name_sub_table, name_sub_table, "name_sub_table"); gs_private_st_composite(st_name_string_sub_table, name_string_sub_table_t, "name_string_sub_table_t", name_string_sub_enum_ptrs, name_string_sub_reloc_ptrs); gs_private_st_composite(st_name_table, name_table, "name_table", name_table_enum_ptrs, name_table_reloc_ptrs); /* Forward references */ private int name_alloc_sub(P1(name_table *)); private void name_free_sub(P2(name_table *, uint)); private void name_scan_sub(P3(name_table *, uint, bool)); /* Debugging printout */ #ifdef DEBUG private void name_print(const char *msg, const name_table *nt, uint nidx, const int *pflag) { const name_string_t *pnstr = names_index_string_inline(nt, nidx); const name *pname = names_index_ptr_inline(nt, nidx); const byte *str = pnstr->string_bytes; dlprintf1("[n]%s", msg); if (pflag) dprintf1("(%d)", *pflag); dprintf2(" (0x%lx#%u)", (ulong)pname, nidx); debug_print_string(str, pnstr->string_size); dprintf2("(0x%lx,%u)\n", (ulong)str, pnstr->string_size); } # define if_debug_name(msg, nt, nidx, pflag)\ if ( gs_debug_c('n') ) name_print(msg, nt, nidx, pflag) #else # define if_debug_name(msg, nt, nidx, pflag) DO_NOTHING #endif /* Initialize a name table */ name_table * names_init(ulong count, gs_ref_memory_t *imem) { gs_memory_t *mem = (gs_memory_t *)imem; name_table *nt; int i; if (count == 0) count = max_name_count + 1L; else if (count - 1 > max_name_count) return 0; nt = gs_alloc_struct(mem, name_table, &st_name_table, "name_init(nt)"); if (nt == 0) return 0; memset(nt, 0, sizeof(name_table)); nt->max_sub_count = ((count - 1) | nt_sub_index_mask) >> nt_log2_sub_size; nt->name_string_attrs = imemory_space(imem) | a_readonly; nt->memory = mem; /* Initialize the one-character names. */ /* Start by creating the necessary sub-tables. */ for (i = 0; i < NT_1CHAR_FIRST + NT_1CHAR_SIZE; i += nt_sub_size) { int code = name_alloc_sub(nt); if (code < 0) { while (nt->sub_next > 0) name_free_sub(nt, --(nt->sub_next)); gs_free_object(mem, nt, "name_init(nt)"); return 0; } } for (i = -1; i < NT_1CHAR_SIZE; i++) { uint ncnt = NT_1CHAR_FIRST + i; uint nidx = name_count_to_index(ncnt); name *pname = names_index_ptr_inline(nt, nidx); name_string_t *pnstr = names_index_string_inline(nt, nidx); if (i < 0) pnstr->string_bytes = nt_1char_names, pnstr->string_size = 0; else pnstr->string_bytes = nt_1char_names + i, pnstr->string_size = 1; pnstr->foreign_string = 1; pnstr->mark = 1; pname->pvalue = pv_no_defn; } nt->perm_count = NT_1CHAR_FIRST + NT_1CHAR_SIZE; /* Reconstruct the free list. */ nt->free = 0; names_trace_finish(nt, NULL); return nt; } /* Get the allocator for the name table. */ gs_memory_t * names_memory(const name_table * nt) { return nt->memory; } /* Look up or enter a name in the table. */ /* Return 0 or an error code. */ /* The return may overlap the characters of the string! */ /* See iname.h for the meaning of enterflag. */ int names_ref(name_table *nt, const byte *ptr, uint size, ref *pref, int enterflag) { name *pname; name_string_t *pnstr; uint nidx; uint *phash; /* Compute a hash for the string. */ /* Make a special check for 1-character names. */ switch (size) { case 0: nidx = name_count_to_index(1); pname = names_index_ptr_inline(nt, nidx); goto mkn; case 1: if (*ptr < NT_1CHAR_SIZE) { uint hash = *ptr + NT_1CHAR_FIRST; nidx = name_count_to_index(hash); pname = names_index_ptr_inline(nt, nidx); goto mkn; } /* falls through */ default: { uint hash; NAME_HASH(hash, hash_permutation, ptr, size); phash = nt->hash + (hash & (NT_HASH_SIZE - 1)); } } for (nidx = *phash; nidx != 0; nidx = name_next_index(nidx, pnstr) ) { pnstr = names_index_string_inline(nt, nidx); if (pnstr->string_size == size && !memcmp_inline(ptr, pnstr->string_bytes, size) ) { pname = name_index_ptr_inline(nt, nidx); goto mkn; } } /* Name was not in the table. Make a new entry. */ if (enterflag < 0) return_error(e_undefined); if (size > max_name_string) return_error(e_limitcheck); nidx = nt->free; if (nidx == 0) { int code = name_alloc_sub(nt); if (code < 0) return code; nidx = nt->free; } pnstr = names_index_string_inline(nt, nidx); if (enterflag == 1) { byte *cptr = (byte *)gs_alloc_string(nt->memory, size, "names_ref(string)"); if (cptr == 0) return_error(e_VMerror); memcpy(cptr, ptr, size); pnstr->string_bytes = cptr; pnstr->foreign_string = 0; } else { pnstr->string_bytes = ptr; pnstr->foreign_string = (enterflag == 0 ? 1 : 0); } pnstr->string_size = size; pname = name_index_ptr_inline(nt, nidx); pname->pvalue = pv_no_defn; nt->free = name_next_index(nidx, pnstr); set_name_next_index(nidx, pnstr, *phash); *phash = nidx; if_debug_name("new name", nt, nidx, &enterflag); mkn: make_name(pref, nidx, pname); return 0; } /* Get the string for a name. */ void names_string_ref(const name_table * nt, const ref * pnref /* t_name */ , ref * psref /* result, t_string */ ) { const name_string_t *pnstr = names_string_inline(nt, pnref); make_const_string(psref, (pnstr->foreign_string ? avm_foreign | a_readonly : nt->name_string_attrs), pnstr->string_size, (const byte *)pnstr->string_bytes); } /* Convert a t_string object to a name. */ /* Copy the executable attribute. */ int names_from_string(name_table * nt, const ref * psref, ref * pnref) { int exec = r_has_attr(psref, a_executable); int code = names_ref(nt, psref->value.bytes, r_size(psref), pnref, 1); if (code < 0) return code; if (exec) r_set_attrs(pnref, a_executable); return code; } /* Enter a (permanently allocated) C string as a name. */ int names_enter_string(name_table * nt, const char *str, ref * pref) { return names_ref(nt, (const byte *)str, strlen(str), pref, 0); } /* Invalidate the value cache for a name. */ void names_invalidate_value_cache(name_table * nt, const ref * pnref) { pnref->value.pname->pvalue = pv_other; } /* Convert between names and indices. */ #undef names_index name_index_t names_index(const name_table * nt, const ref * pnref) { return names_index_inline(nt, pnref); } void names_index_ref(const name_table * nt, name_index_t index, ref * pnref) { names_index_ref_inline(nt, index, pnref); } name * names_index_ptr(const name_table * nt, name_index_t index) { return names_index_ptr_inline(nt, index); } /* Get the index of the next valid name. */ /* The argument is 0 or a valid index. */ /* Return 0 if there are no more. */ name_index_t names_next_valid_index(name_table * nt, name_index_t nidx) { const name_string_sub_table_t *ssub = nt->sub[nidx >> nt_log2_sub_size].strings; const name_string_t *pnstr; do { ++nidx; if ((nidx & nt_sub_index_mask) == 0) for (;; nidx += nt_sub_size) { if ((nidx >> nt_log2_sub_size) >= nt->sub_count) return 0; ssub = nt->sub[nidx >> nt_log2_sub_size].strings; if (ssub != 0) break; } pnstr = &ssub->strings[nidx & nt_sub_index_mask]; } while (pnstr->string_bytes == 0); return nidx; } /* ------ Garbage collection ------ */ /* Unmark all non-permanent names before a garbage collection. */ void names_unmark_all(name_table * nt) { uint si; name_string_sub_table_t *ssub; for (si = 0; si < nt->sub_count; ++si) if ((ssub = nt->sub[si].strings) != 0) { uint i; /* We can make the test much more efficient if we want.... */ for (i = 0; i < nt_sub_size; ++i) if (name_index_to_count((si << nt_log2_sub_size) + i) >= nt->perm_count) ssub->strings[i].mark = 0; } } /* Mark a name. Return true if new mark. We export this so we can mark */ /* character names in the character cache. */ bool names_mark_index(name_table * nt, name_index_t nidx) { name_string_t *pnstr = names_index_string_inline(nt, nidx); if (pnstr->mark) return false; pnstr->mark = 1; return true; } /* Get the object (sub-table) containing a name. */ /* The garbage collector needs this so it can relocate pointers to names. */ void /*obj_header_t */ * names_ref_sub_table(name_table * nt, const ref * pnref) { /* When this procedure is called, the pointers from the name table */ /* to the sub-tables may or may not have been relocated already, */ /* so we can't use them. Instead, we have to work backwards from */ /* the name pointer itself. */ return pnref->value.pname - (r_size(pnref) & nt_sub_index_mask); } void /*obj_header_t */ * names_index_sub_table(name_table * nt, name_index_t index) { return nt->sub[index >> nt_log2_sub_size].names; } void /*obj_header_t */ * names_index_string_sub_table(name_table * nt, name_index_t index) { return nt->sub[index >> nt_log2_sub_size].strings; } /* * Clean up the name table after the trace/mark phase of a garbage * collection, by removing names that aren't marked. gcst == NULL indicates * we're doing this for initialization or restore rather than for a GC. */ void names_trace_finish(name_table * nt, gc_state_t * gcst) { uint *phash = &nt->hash[0]; uint i; for (i = 0; i < NT_HASH_SIZE; phash++, i++) { name_index_t prev = 0; /* * The following initialization is only to pacify compilers: * pnprev is only referenced if prev has been set in the loop, * in which case pnprev is also set. */ name_string_t *pnprev = 0; name_index_t nidx = *phash; while (nidx != 0) { name_string_t *pnstr = names_index_string_inline(nt, nidx); name_index_t next = name_next_index(nidx, pnstr); if (pnstr->mark) { prev = nidx; pnprev = pnstr; } else { if_debug_name("GC remove name", nt, nidx, NULL); /* Zero out the string data for the GC. */ pnstr->string_bytes = 0; pnstr->string_size = 0; if (prev == 0) *phash = next; else set_name_next_index(prev, pnprev, next); } nidx = next; } } /* Reconstruct the free list. */ nt->free = 0; for (i = nt->sub_count; i--;) { name_sub_table *sub = nt->sub[i].names; name_string_sub_table_t *ssub = nt->sub[i].strings; if (sub != 0) { name_scan_sub(nt, i, true); if (nt->sub[i].names == 0 && gcst != 0) { /* Mark the just-freed sub-table as unmarked. */ o_set_unmarked((obj_header_t *)sub - 1); o_set_unmarked((obj_header_t *)ssub - 1); } } if (i == 0) break; } nt->sub_next = 0; } /* ------ Save/restore ------ */ /* Clean up the name table before a restore. */ /* Currently, this is never called, because the name table is allocated */ /* in system VM. However, for a Level 1 system, we might choose to */ /* allocate the name table in global VM; in this case, this routine */ /* would be called before doing the global part of a top-level restore. */ /* Currently we don't make any attempt to optimize this. */ void names_restore(name_table * nt, alloc_save_t * save) { /* We simply mark all names older than the save, */ /* and let names_trace_finish sort everything out. */ uint si; for (si = 0; si < nt->sub_count; ++si) if (nt->sub[si].strings != 0) { uint i; for (i = 0; i < nt_sub_size; ++i) { name_string_t *pnstr = names_index_string_inline(nt, (si << nt_log2_sub_size) + i); if (pnstr->string_bytes == 0) pnstr->mark = 0; else if (pnstr->foreign_string) { /* Avoid storing into a read-only name string. */ if (!pnstr->mark) pnstr->mark = 1; } else pnstr->mark = !alloc_is_since_save(pnstr->string_bytes, save); } } names_trace_finish(nt, NULL); } /* ------ Internal procedures ------ */ /* Allocate the next sub-table. */ private int name_alloc_sub(name_table * nt) { gs_memory_t *mem = nt->memory; uint sub_index = nt->sub_next; name_sub_table *sub; name_string_sub_table_t *ssub; for (;; ++sub_index) { if (sub_index > nt->max_sub_count) return_error(e_limitcheck); if (nt->sub[sub_index].names == 0) break; } nt->sub_next = sub_index + 1; if (nt->sub_next > nt->sub_count) nt->sub_count = nt->sub_next; sub = gs_alloc_struct(mem, name_sub_table, &st_name_sub_table, "name_alloc_sub(sub-table)"); ssub = gs_alloc_struct(mem, name_string_sub_table_t, &st_name_string_sub_table, "name_alloc_sub(string sub-table)"); if (sub == 0 || ssub == 0) { gs_free_object(mem, ssub, "name_alloc_sub(string sub-table)"); gs_free_object(mem, sub, "name_alloc_sub(sub-table)"); return_error(e_VMerror); } memset(sub, 0, sizeof(name_sub_table)); memset(ssub, 0, sizeof(name_string_sub_table_t)); /* The following code is only used if EXTEND_NAMES is non-zero. */ #if name_extension_bits > 0 sub->high_index = (sub_index >> (16 - nt_log2_sub_size)) << 16; #endif nt->sub[sub_index].names = sub; nt->sub[sub_index].strings = ssub; /* Add the newly allocated entries to the free list. */ /* Note that the free list will only be properly sorted if */ /* it was empty initially. */ name_scan_sub(nt, sub_index, false); #ifdef DEBUG if (gs_debug_c('n')) { /* Print the lengths of the hash chains. */ int i0; for (i0 = 0; i0 < NT_HASH_SIZE; i0 += 16) { int i; dlprintf1("[n]chain %d:", i0); for (i = i0; i < i0 + 16; i++) { int n = 0; uint nidx; for (nidx = nt->hash[i]; nidx != 0; nidx = name_next_index(nidx, names_index_string_inline(nt, nidx)) ) n++; dprintf1(" %d", n); } dputc('\n'); } } #endif return 0; } /* Free a sub-table. */ private void name_free_sub(name_table * nt, uint sub_index) { gs_free_object(nt->memory, nt->sub[sub_index].strings, "name_free_sub(string sub-table)"); gs_free_object(nt->memory, nt->sub[sub_index].names, "name_free_sub(sub-table)"); nt->sub[sub_index].names = 0; nt->sub[sub_index].strings = 0; } /* Scan a sub-table and add unmarked entries to the free list. */ /* We add the entries in decreasing count order, so the free list */ /* will stay sorted. If all entries are unmarked and free_empty is true, */ /* free the sub-table. */ private void name_scan_sub(name_table * nt, uint sub_index, bool free_empty) { name_string_sub_table_t *ssub = nt->sub[sub_index].strings; uint free = nt->free; uint nbase = sub_index << nt_log2_sub_size; uint ncnt = nbase + (nt_sub_size - 1); bool keep = !free_empty; if (ssub == 0) return; if (nbase == 0) nbase = 1, keep = true; /* don't free name 0 */ for (;; --ncnt) { uint nidx = name_count_to_index(ncnt); name_string_t *pnstr = &ssub->strings[nidx & nt_sub_index_mask]; if (pnstr->mark) keep = true; else { set_name_next_index(nidx, pnstr, free); free = nidx; } if (ncnt == nbase) break; } if (keep) nt->free = free; else { /* No marked entries, free the sub-table. */ name_free_sub(nt, sub_index); if (sub_index == nt->sub_count - 1) { /* Back up over a final run of deleted sub-tables. */ do { --sub_index; } while (nt->sub[sub_index].names == 0); nt->sub_count = sub_index + 1; if (nt->sub_next > sub_index) nt->sub_next = sub_index; } else if (nt->sub_next == sub_index) nt->sub_next--; } } /* Garbage collector enumeration and relocation procedures. */ private ENUM_PTRS_BEGIN_PROC(name_table_enum_ptrs) { EV_CONST name_table *const nt = vptr; uint i = index >> 1; if (i >= nt->sub_count) return 0; if (index & 1) ENUM_RETURN(nt->sub[i].strings); else ENUM_RETURN(nt->sub[i].names); } ENUM_PTRS_END_PROC private RELOC_PTRS_WITH(name_table_reloc_ptrs, name_table *nt) { uint sub_count = nt->sub_count; uint i; /* Now we can relocate the sub-table pointers. */ for (i = 0; i < sub_count; i++) { RELOC_VAR(nt->sub[i].names); RELOC_VAR(nt->sub[i].strings); } /* * We also need to relocate the cached value pointers. * We don't do this here, but in a separate scan over the * permanent dictionaries, at the very end of garbage collection. */ } RELOC_PTRS_END private ENUM_PTRS_BEGIN_PROC(name_string_sub_enum_ptrs) { return 0; } ENUM_PTRS_END_PROC private RELOC_PTRS_BEGIN(name_string_sub_reloc_ptrs) { name_string_t *pnstr = ((name_string_sub_table_t *)vptr)->strings; uint i; for (i = 0; i < nt_sub_size; ++pnstr, ++i) { if (pnstr->string_bytes != 0 && !pnstr->foreign_string) { gs_const_string nstr; nstr.data = pnstr->string_bytes; nstr.size = pnstr->string_size; RELOC_CONST_STRING_VAR(nstr); pnstr->string_bytes = nstr.data; } } } RELOC_PTRS_END