blob: ad0a4fc98c39b13337fe183b7ab2514b8e44846c [file] [log] [blame]
/*
* Heap stringtable handling, string interning.
*/
#include "duk_internal.h"
#if defined(DUK_USE_STRTAB_PROBE)
#define DUK__HASH_INITIAL(hash,h_size) DUK_STRTAB_HASH_INITIAL((hash),(h_size))
#define DUK__HASH_PROBE_STEP(hash) DUK_STRTAB_HASH_PROBE_STEP((hash))
#define DUK__DELETED_MARKER(heap) DUK_STRTAB_DELETED_MARKER((heap))
#endif
#if defined(DUK_USE_MARK_AND_SWEEP)
#define DUK__PREVENT_MS_SIDE_EFFECTS(heap) do { \
(heap)->mark_and_sweep_base_flags |= \
DUK_MS_FLAG_NO_STRINGTABLE_RESIZE | /* avoid recursive string table call */ \
DUK_MS_FLAG_NO_FINALIZERS | /* avoid pressure to add/remove strings, invalidation of call data argument, etc. */ \
DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* avoid array abandoning which interns strings */ \
} while (0)
#endif
/*
* Create a hstring and insert into the heap. The created object
* is directly garbage collectable with reference count zero.
*
* The caller must place the interned string into the stringtable
* immediately (without chance of a longjmp); otherwise the string
* is lost.
*/
DUK_LOCAL
duk_hstring *duk__alloc_init_hstring(duk_heap *heap,
const duk_uint8_t *str,
duk_uint32_t blen,
duk_uint32_t strhash,
const duk_uint8_t *extdata) {
duk_hstring *res = NULL;
duk_uint8_t *data;
duk_size_t alloc_size;
duk_uarridx_t dummy;
duk_uint32_t clen;
#if defined(DUK_USE_STRLEN16)
/* If blen <= 0xffffUL, clen is also guaranteed to be <= 0xffffUL. */
if (blen > 0xffffUL) {
DUK_D(DUK_DPRINT("16-bit string blen/clen active and blen over 16 bits, reject intern"));
return NULL;
}
#endif
if (extdata) {
alloc_size = (duk_size_t) sizeof(duk_hstring_external);
res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
if (!res) {
goto alloc_error;
}
DUK_MEMZERO(res, sizeof(duk_hstring_external));
#if defined(DUK_USE_EXPLICIT_NULL_INIT)
DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
#endif
DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, DUK_HSTRING_FLAG_EXTDATA);
((duk_hstring_external *) res)->extdata = extdata;
} else {
/* NUL terminate for convenient C access */
alloc_size = (duk_size_t) (sizeof(duk_hstring) + blen + 1);
res = (duk_hstring *) DUK_ALLOC(heap, alloc_size);
if (!res) {
goto alloc_error;
}
DUK_MEMZERO(res, sizeof(duk_hstring));
#if defined(DUK_USE_EXPLICIT_NULL_INIT)
DUK_HEAPHDR_STRING_INIT_NULLS(&res->hdr);
#endif
DUK_HEAPHDR_SET_TYPE_AND_FLAGS(&res->hdr, DUK_HTYPE_STRING, 0);
data = (duk_uint8_t *) (res + 1);
DUK_MEMCPY(data, str, blen);
data[blen] = (duk_uint8_t) 0;
}
DUK_ASSERT(!DUK_HSTRING_HAS_ARRIDX(res));
if (duk_js_to_arrayindex_raw_string(str, blen, &dummy)) {
DUK_HSTRING_SET_ARRIDX(res);
}
/* All strings beginning with 0xff are treated as "internal",
* even strings interned by the user. This allows user code to
* create internal properties too, and makes behavior consistent
* in case user code happens to use a string also used by Duktape
* (such as string has already been interned and has the 'internal'
* flag set).
*/
DUK_ASSERT(!DUK_HSTRING_HAS_INTERNAL(res));
if (blen > 0 && str[0] == (duk_uint8_t) 0xff) {
DUK_HSTRING_SET_INTERNAL(res);
}
DUK_HSTRING_SET_HASH(res, strhash);
DUK_HSTRING_SET_BYTELEN(res, blen);
clen = (duk_uint32_t) duk_unicode_unvalidated_utf8_length(str, (duk_size_t) blen);
DUK_ASSERT(clen <= blen);
#if defined(DUK_USE_HSTRING_CLEN)
DUK_HSTRING_SET_CHARLEN(res, clen);
#endif
/* Using an explicit 'ASCII' flag has larger footprint (one call site
* only) but is quite useful for the case when there's no explicit
* 'clen' in duk_hstring.
*/
DUK_ASSERT(!DUK_HSTRING_HAS_ASCII(res));
if (clen == blen) {
DUK_HSTRING_SET_ASCII(res);
}
DUK_DDD(DUK_DDDPRINT("interned string, hash=0x%08lx, blen=%ld, clen=%ld, has_arridx=%ld, has_extdata=%ld",
(unsigned long) DUK_HSTRING_GET_HASH(res),
(long) DUK_HSTRING_GET_BYTELEN(res),
(long) DUK_HSTRING_GET_CHARLEN(res),
(long) (DUK_HSTRING_HAS_ARRIDX(res) ? 1 : 0),
(long) (DUK_HSTRING_HAS_EXTDATA(res) ? 1 : 0)));
return res;
alloc_error:
DUK_FREE(heap, res);
return NULL;
}
/*
* String table algorithm: fixed size string table with array chaining
*
* The top level string table has a fixed size, with each slot holding
* either NULL, string pointer, or pointer to a separately allocated
* string pointer list.
*
* This is good for low memory environments using a pool allocator: the
* top level allocation has a fixed size and the pointer lists have quite
* small allocation size, which further matches the typical pool sizes
* needed by objects, strings, property tables, etc.
*/
#if defined(DUK_USE_STRTAB_CHAIN)
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_uint16_t *lst;
duk_uint16_t *new_lst;
duk_size_t i, n;
duk_uint16_t null16 = heap->heapptr_null16;
duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
DUK_ASSERT(heap != NULL);
DUK_ASSERT(h != NULL);
slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
if (e->u.str16 == null16) {
e->u.str16 = h16;
} else {
/* Now two entries in the same slot, alloc list */
lst = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * 2);
if (lst == NULL) {
return 1; /* fail */
}
lst[0] = e->u.str16;
lst[1] = h16;
e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) lst);
e->listlen = 2;
}
} else {
DUK_ASSERT(e->u.strlist16 != null16);
lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
DUK_ASSERT(lst != NULL);
for (i = 0, n = e->listlen; i < n; i++) {
if (lst[i] == null16) {
lst[i] = h16;
return 0;
}
}
if (e->listlen + 1 == 0) {
/* Overflow, relevant mainly when listlen is 16 bits. */
return 1; /* fail */
}
new_lst = (duk_uint16_t *) DUK_REALLOC(heap, lst, sizeof(duk_uint16_t) * (e->listlen + 1));
if (new_lst == NULL) {
return 1; /* fail */
}
new_lst[e->listlen++] = h16;
e->u.strlist16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) new_lst);
}
return 0;
}
#else /* DUK_USE_HEAPPTR16 */
DUK_LOCAL duk_bool_t duk__insert_hstring_chain(duk_heap *heap, duk_hstring *h) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_hstring **lst;
duk_hstring **new_lst;
duk_size_t i, n;
DUK_ASSERT(heap != NULL);
DUK_ASSERT(h != NULL);
slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
if (e->u.str == NULL) {
e->u.str = h;
} else {
/* Now two entries in the same slot, alloc list */
lst = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * 2);
if (lst == NULL) {
return 1; /* fail */
}
lst[0] = e->u.str;
lst[1] = h;
e->u.strlist = lst;
e->listlen = 2;
}
} else {
DUK_ASSERT(e->u.strlist != NULL);
lst = e->u.strlist;
for (i = 0, n = e->listlen; i < n; i++) {
if (lst[i] == NULL) {
lst[i] = h;
return 0;
}
}
if (e->listlen + 1 == 0) {
/* Overflow, relevant mainly when listlen is 16 bits. */
return 1; /* fail */
}
new_lst = (duk_hstring **) DUK_REALLOC(heap, e->u.strlist, sizeof(duk_hstring *) * (e->listlen + 1));
if (new_lst == NULL) {
return 1; /* fail */
}
new_lst[e->listlen++] = h;
e->u.strlist = new_lst;
}
return 0;
}
#endif /* DUK_USE_HEAPPTR16 */
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_uint16_t *lst;
duk_size_t i, n;
duk_uint16_t null16 = heap->heapptr_null16;
DUK_ASSERT(heap != NULL);
slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
if (e->u.str16 != null16) {
duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
DUK_ASSERT(h != NULL);
if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
return h;
}
}
} else {
DUK_ASSERT(e->u.strlist16 != null16);
lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
DUK_ASSERT(lst != NULL);
for (i = 0, n = e->listlen; i < n; i++) {
if (lst[i] != null16) {
duk_hstring *h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[i]);
DUK_ASSERT(h != NULL);
if (DUK_HSTRING_GET_BYTELEN(h) == blen &&
DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(h), (size_t) blen) == 0) {
return h;
}
}
}
}
return NULL;
}
#else /* DUK_USE_HEAPPTR16 */
DUK_LOCAL duk_hstring *duk__find_matching_string_chain(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_hstring **lst;
duk_size_t i, n;
DUK_ASSERT(heap != NULL);
slotidx = strhash % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
if (e->u.str != NULL &&
DUK_HSTRING_GET_BYTELEN(e->u.str) == blen &&
DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e->u.str), (size_t) blen) == 0) {
return e->u.str;
}
} else {
DUK_ASSERT(e->u.strlist != NULL);
lst = e->u.strlist;
for (i = 0, n = e->listlen; i < n; i++) {
if (lst[i] != NULL &&
DUK_HSTRING_GET_BYTELEN(lst[i]) == blen &&
DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(lst[i]), (size_t) blen) == 0) {
return lst[i];
}
}
}
return NULL;
}
#endif /* DUK_USE_HEAPPTR16 */
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_uint16_t *lst;
duk_size_t i, n;
duk_uint16_t h16;
duk_uint16_t null16 = heap->heapptr_null16;
DUK_ASSERT(heap != NULL);
DUK_ASSERT(h != NULL);
slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
DUK_ASSERT(h != NULL);
h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
if (e->u.str16 == h16) {
e->u.str16 = null16;
return;
}
} else {
DUK_ASSERT(e->u.strlist16 != null16);
lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
DUK_ASSERT(lst != NULL);
for (i = 0, n = e->listlen; i < n; i++) {
if (lst[i] == h16) {
lst[i] = null16;
return;
}
}
}
DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
DUK_UNREACHABLE();
return;
}
#else /* DUK_USE_HEAPPTR16 */
DUK_LOCAL void duk__remove_matching_hstring_chain(duk_heap *heap, duk_hstring *h) {
duk_small_uint_t slotidx;
duk_strtab_entry *e;
duk_hstring **lst;
duk_size_t i, n;
DUK_ASSERT(heap != NULL);
DUK_ASSERT(h != NULL);
slotidx = DUK_HSTRING_GET_HASH(h) % DUK_STRTAB_CHAIN_SIZE;
DUK_ASSERT(slotidx < DUK_STRTAB_CHAIN_SIZE);
e = heap->strtable + slotidx;
if (e->listlen == 0) {
DUK_ASSERT(h != NULL);
if (e->u.str == h) {
e->u.str = NULL;
return;
}
} else {
DUK_ASSERT(e->u.strlist != NULL);
lst = e->u.strlist;
for (i = 0, n = e->listlen; i < n; i++) {
DUK_ASSERT(h != NULL);
if (lst[i] == h) {
lst[i] = NULL;
return;
}
}
}
DUK_D(DUK_DPRINT("failed to find string that should be in stringtable"));
DUK_UNREACHABLE();
return;
}
#endif /* DUK_USE_HEAPPTR16 */
#if defined(DUK_USE_DEBUG)
DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap) {
duk_strtab_entry *e;
duk_small_uint_t i;
duk_size_t j, n, used;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t *lst;
duk_uint16_t null16 = heap->heapptr_null16;
#else
duk_hstring **lst;
#endif
DUK_ASSERT(heap != NULL);
for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
e = heap->strtable + i;
if (e->listlen == 0) {
#if defined(DUK_USE_HEAPPTR16)
DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str16 != null16 ? 1 : 0)));
#else
DUK_DD(DUK_DDPRINT("[%03d] -> plain %d", (int) i, (int) (e->u.str ? 1 : 0)));
#endif
} else {
used = 0;
#if defined(DUK_USE_HEAPPTR16)
lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
#else
lst = e->u.strlist;
#endif
DUK_ASSERT(lst != NULL);
for (j = 0, n = e->listlen; j < n; j++) {
#if defined(DUK_USE_HEAPPTR16)
if (lst[j] != null16) {
#else
if (lst[j] != NULL) {
#endif
used++;
}
}
DUK_DD(DUK_DDPRINT("[%03d] -> array %d/%d", (int) i, (int) used, (int) e->listlen));
}
}
}
#endif /* DUK_USE_DEBUG */
#endif /* DUK_USE_STRTAB_CHAIN */
/*
* String table algorithm: closed hashing with a probe sequence
*
* This is the default algorithm and works fine for environments with
* minimal memory constraints.
*/
#if defined(DUK_USE_STRTAB_PROBE)
/* Count actually used (non-NULL, non-DELETED) entries. */
DUK_LOCAL duk_int_t duk__count_used_probe(duk_heap *heap) {
duk_int_t res = 0;
duk_uint_fast32_t i, n;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t null16 = heap->heapptr_null16;
duk_uint16_t deleted16 = heap->heapptr_deleted16;
#endif
n = (duk_uint_fast32_t) heap->st_size;
for (i = 0; i < n; i++) {
#if defined(DUK_USE_HEAPPTR16)
if (heap->strtable16[i] != null16 && heap->strtable16[i] != deleted16) {
#else
if (heap->strtable[i] != NULL && heap->strtable[i] != DUK__DELETED_MARKER(heap)) {
#endif
res++;
}
}
return res;
}
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) {
#else
DUK_LOCAL void duk__insert_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_uint32_t *p_used, duk_hstring *h) {
#endif
duk_uint32_t i;
duk_uint32_t step;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t null16 = heap->heapptr_null16;
duk_uint16_t deleted16 = heap->heapptr_deleted16;
#endif
DUK_ASSERT(size > 0);
i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size);
step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(h));
for (;;) {
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t e16 = entries16[i];
#else
duk_hstring *e = entries[i];
#endif
#if defined(DUK_USE_HEAPPTR16)
/* XXX: could check for e16 == 0 because NULL is guaranteed to
* encode to zero.
*/
if (e16 == null16) {
#else
if (e == NULL) {
#endif
DUK_DDD(DUK_DDDPRINT("insert hit (null): %ld", (long) i));
#if defined(DUK_USE_HEAPPTR16)
entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
#else
entries[i] = h;
#endif
(*p_used)++;
break;
#if defined(DUK_USE_HEAPPTR16)
} else if (e16 == deleted16) {
#else
} else if (e == DUK__DELETED_MARKER(heap)) {
#endif
/* st_used remains the same, DELETED is counted as used */
DUK_DDD(DUK_DDDPRINT("insert hit (deleted): %ld", (long) i));
#if defined(DUK_USE_HEAPPTR16)
entries16[i] = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
#else
entries[i] = h;
#endif
break;
}
DUK_DDD(DUK_DDDPRINT("insert miss: %ld", (long) i));
i = (i + step) % size;
/* looping should never happen */
DUK_ASSERT(i != DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(h), size));
}
}
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
#else
DUK_LOCAL duk_hstring *duk__find_matching_string_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
#endif
duk_uint32_t i;
duk_uint32_t step;
DUK_ASSERT(size > 0);
i = DUK__HASH_INITIAL(strhash, size);
step = DUK__HASH_PROBE_STEP(strhash);
for (;;) {
duk_hstring *e;
#if defined(DUK_USE_HEAPPTR16)
e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, entries16[i]);
#else
e = entries[i];
#endif
if (!e) {
return NULL;
}
if (e != DUK__DELETED_MARKER(heap) && DUK_HSTRING_GET_BYTELEN(e) == blen) {
if (DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(e), (size_t) blen) == 0) {
DUK_DDD(DUK_DDDPRINT("find matching hit: %ld (step %ld, size %ld)",
(long) i, (long) step, (long) size));
return e;
}
}
DUK_DDD(DUK_DDDPRINT("find matching miss: %ld (step %ld, size %ld)",
(long) i, (long) step, (long) size));
i = (i + step) % size;
/* looping should never happen */
DUK_ASSERT(i != DUK__HASH_INITIAL(strhash, size));
}
DUK_UNREACHABLE();
}
#if defined(DUK_USE_HEAPPTR16)
DUK_LOCAL void duk__remove_matching_hstring_probe(duk_heap *heap, duk_uint16_t *entries16, duk_uint32_t size, duk_hstring *h) {
#else
DUK_LOCAL void duk__remove_matching_hstring_probe(duk_heap *heap, duk_hstring **entries, duk_uint32_t size, duk_hstring *h) {
#endif
duk_uint32_t i;
duk_uint32_t step;
duk_uint32_t hash;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t null16 = heap->heapptr_null16;
duk_uint16_t h16 = DUK_USE_HEAPPTR_ENC16(heap->heap_udata, (void *) h);
#endif
DUK_ASSERT(size > 0);
hash = DUK_HSTRING_GET_HASH(h);
i = DUK__HASH_INITIAL(hash, size);
step = DUK__HASH_PROBE_STEP(hash);
for (;;) {
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t e16 = entries16[i];
#else
duk_hstring *e = entries[i];
#endif
#if defined(DUK_USE_HEAPPTR16)
if (e16 == null16) {
#else
if (!e) {
#endif
DUK_UNREACHABLE();
break;
}
#if defined(DUK_USE_HEAPPTR16)
if (e16 == h16) {
#else
if (e == h) {
#endif
/* st_used remains the same, DELETED is counted as used */
DUK_DDD(DUK_DDDPRINT("free matching hit: %ld", (long) i));
#if defined(DUK_USE_HEAPPTR16)
entries16[i] = heap->heapptr_deleted16;
#else
entries[i] = DUK__DELETED_MARKER(heap);
#endif
break;
}
DUK_DDD(DUK_DDDPRINT("free matching miss: %ld", (long) i));
i = (i + step) % size;
/* looping should never happen */
DUK_ASSERT(i != DUK__HASH_INITIAL(hash, size));
}
}
DUK_LOCAL duk_bool_t duk__resize_strtab_raw_probe(duk_heap *heap, duk_uint32_t new_size) {
#if defined(DUK_USE_DEBUG)
duk_uint32_t old_used = heap->st_used;
#endif
duk_uint32_t old_size = heap->st_size;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t *old_entries = heap->strtable16;
duk_uint16_t *new_entries = NULL;
#else
duk_hstring **old_entries = heap->strtable;
duk_hstring **new_entries = NULL;
#endif
duk_uint32_t new_used = 0;
duk_uint32_t i;
#if defined(DUK_USE_DEBUG)
DUK_UNREF(old_used); /* unused with some debug level combinations */
#endif
#ifdef DUK_USE_DDDPRINT
DUK_DDD(DUK_DDDPRINT("attempt to resize stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
(long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
(long) (((double) old_used) / ((double) old_size) * 100.0),
(long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) duk__count_used_probe(heap),
(long) (((double) duk__count_used_probe(heap)) / ((double) new_size) * 100.0)));
#endif
DUK_ASSERT(new_size > (duk_uint32_t) duk__count_used_probe(heap)); /* required for rehash to succeed, equality not that useful */
DUK_ASSERT(old_entries);
/*
* The attempt to allocate may cause a GC. Such a GC must not attempt to resize
* the stringtable (though it can be swept); finalizer execution and object
* compaction must also be postponed to avoid the pressure to add strings to the
* string table. Call site must prevent these.
*/
#if defined(DUK_USE_MARK_AND_SWEEP)
DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE);
DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_FINALIZERS);
DUK_ASSERT(heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_OBJECT_COMPACTION);
#endif
#if defined(DUK_USE_HEAPPTR16)
new_entries = (duk_uint16_t *) DUK_ALLOC(heap, sizeof(duk_uint16_t) * new_size);
#else
new_entries = (duk_hstring **) DUK_ALLOC(heap, sizeof(duk_hstring *) * new_size);
#endif
if (!new_entries) {
goto resize_error;
}
#if defined(DUK_USE_EXPLICIT_NULL_INIT)
for (i = 0; i < new_size; i++) {
#if defined(DUK_USE_HEAPPTR16)
new_entries[i] = heap->heapptr_null16;
#else
new_entries[i] = NULL;
#endif
}
#else
#if defined(DUK_USE_HEAPPTR16)
/* Relies on NULL encoding to zero. */
DUK_MEMZERO(new_entries, sizeof(duk_uint16_t) * new_size);
#else
DUK_MEMZERO(new_entries, sizeof(duk_hstring *) * new_size);
#endif
#endif
/* Because new_size > duk__count_used_probe(heap), guaranteed to work */
for (i = 0; i < old_size; i++) {
duk_hstring *e;
#if defined(DUK_USE_HEAPPTR16)
e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, old_entries[i]);
#else
e = old_entries[i];
#endif
if (e == NULL || e == DUK__DELETED_MARKER(heap)) {
continue;
}
/* checking for DUK__DELETED_MARKER is not necessary here, but helper does it now */
duk__insert_hstring_probe(heap, new_entries, new_size, &new_used, e);
}
#ifdef DUK_USE_DDPRINT
DUK_DD(DUK_DDPRINT("resized stringtable: %ld entries, %ld bytes, %ld used, %ld%% load -> %ld entries, %ld bytes, %ld used, %ld%% load",
(long) old_size, (long) (sizeof(duk_hstring *) * old_size), (long) old_used,
(long) (((double) old_used) / ((double) old_size) * 100.0),
(long) new_size, (long) (sizeof(duk_hstring *) * new_size), (long) new_used,
(long) (((double) new_used) / ((double) new_size) * 100.0)));
#endif
#if defined(DUK_USE_HEAPPTR16)
DUK_FREE(heap, heap->strtable16);
heap->strtable16 = new_entries;
#else
DUK_FREE(heap, heap->strtable);
heap->strtable = new_entries;
#endif
heap->st_size = new_size;
heap->st_used = new_used; /* may be less, since DELETED entries are NULLed by rehash */
return 0; /* OK */
resize_error:
DUK_FREE(heap, new_entries);
return 1; /* FAIL */
}
DUK_LOCAL duk_bool_t duk__resize_strtab_probe(duk_heap *heap) {
duk_uint32_t new_size;
duk_bool_t ret;
new_size = (duk_uint32_t) duk__count_used_probe(heap);
if (new_size >= 0x80000000UL) {
new_size = DUK_STRTAB_HIGHEST_32BIT_PRIME;
} else {
new_size = duk_util_get_hash_prime(DUK_STRTAB_GROW_ST_SIZE(new_size));
new_size = duk_util_get_hash_prime(new_size);
}
DUK_ASSERT(new_size > 0);
/* rehash even if old and new sizes are the same to get rid of
* DELETED entries.
*/
ret = duk__resize_strtab_raw_probe(heap, new_size);
return ret;
}
DUK_LOCAL duk_bool_t duk__recheck_strtab_size_probe(duk_heap *heap, duk_uint32_t new_used) {
duk_uint32_t new_free;
duk_uint32_t tmp1;
duk_uint32_t tmp2;
DUK_ASSERT(new_used <= heap->st_size); /* grow by at most one */
new_free = heap->st_size - new_used; /* unsigned intentionally */
/* new_free / size <= 1 / DIV <=> new_free <= size / DIV */
/* new_used / size <= 1 / DIV <=> new_used <= size / DIV */
tmp1 = heap->st_size / DUK_STRTAB_MIN_FREE_DIVISOR;
tmp2 = heap->st_size / DUK_STRTAB_MIN_USED_DIVISOR;
if (new_free <= tmp1 || new_used <= tmp2) {
/* load factor too low or high, count actually used entries and resize */
return duk__resize_strtab_probe(heap);
} else {
return 0; /* OK */
}
}
#if defined(DUK_USE_DEBUG)
DUK_INTERNAL void duk_heap_dump_strtab(duk_heap *heap) {
duk_uint32_t i;
duk_hstring *h;
DUK_ASSERT(heap != NULL);
#if defined(DUK_USE_HEAPPTR16)
DUK_ASSERT(heap->strtable16 != NULL);
#else
DUK_ASSERT(heap->strtable != NULL);
#endif
DUK_UNREF(h);
for (i = 0; i < heap->st_size; i++) {
#if defined(DUK_USE_HEAPPTR16)
h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]);
#else
h = heap->strtable[i];
#endif
DUK_DD(DUK_DDPRINT("[%03d] -> %p", (int) i, (void *) h));
}
}
#endif /* DUK_USE_DEBUG */
#endif /* DUK_USE_STRTAB_PROBE */
/*
* Raw intern and lookup
*/
DUK_LOCAL duk_hstring *duk__do_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t strhash) {
duk_hstring *res;
const duk_uint8_t *extdata;
#if defined(DUK_USE_MARK_AND_SWEEP)
duk_small_uint_t prev_mark_and_sweep_base_flags;
#endif
/* Prevent any side effects on the string table and the caller provided
* str/blen arguments while interning is in progress. For example, if
* the caller provided str/blen from a dynamic buffer, a finalizer might
* resize that dynamic buffer, invalidating the call arguments.
*/
#if defined(DUK_USE_MARK_AND_SWEEP)
DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
DUK__PREVENT_MS_SIDE_EFFECTS(heap);
#endif
#if defined(DUK_USE_STRTAB_PROBE)
if (duk__recheck_strtab_size_probe(heap, heap->st_used + 1)) {
goto failed;
}
#endif
/* For manual testing only. */
#if 0
{
duk_size_t i;
DUK_PRINTF("INTERN: \"");
for (i = 0; i < blen; i++) {
duk_uint8_t x = str[i];
if (x >= 0x20 && x <= 0x7e && x != '"' && x != '\\') {
DUK_PRINTF("%c", (int) x); /* char: use int cast */
} else {
DUK_PRINTF("\\x%02lx", (long) x);
}
}
DUK_PRINTF("\"\n");
}
#endif
#if defined(DUK_USE_HSTRING_EXTDATA) && defined(DUK_USE_EXTSTR_INTERN_CHECK)
extdata = (const duk_uint8_t *) DUK_USE_EXTSTR_INTERN_CHECK(heap->heap_udata, (void *) DUK_LOSE_CONST(str), (duk_size_t) blen);
#else
extdata = (const duk_uint8_t *) NULL;
#endif
res = duk__alloc_init_hstring(heap, str, blen, strhash, extdata);
if (!res) {
goto failed;
}
#if defined(DUK_USE_STRTAB_CHAIN)
if (duk__insert_hstring_chain(heap, res)) {
/* failed */
DUK_FREE(heap, res);
goto failed;
}
#elif defined(DUK_USE_STRTAB_PROBE)
/* guaranteed to succeed */
duk__insert_hstring_probe(heap,
#if defined(DUK_USE_HEAPPTR16)
heap->strtable16,
#else
heap->strtable,
#endif
heap->st_size,
&heap->st_used,
res);
#else
#error internal error, invalid strtab options
#endif
/* Note: hstring is in heap but has refcount zero and is not strongly reachable.
* Caller should increase refcount and make the hstring reachable before any
* operations which require allocation (and possible gc).
*/
done:
#if defined(DUK_USE_MARK_AND_SWEEP)
heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
#endif
return res;
failed:
res = NULL;
goto done;
}
DUK_LOCAL duk_hstring *duk__do_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen, duk_uint32_t *out_strhash) {
duk_hstring *res;
DUK_ASSERT(out_strhash);
*out_strhash = duk_heap_hashstring(heap, str, (duk_size_t) blen);
#if defined(DUK_USE_ROM_STRINGS)
{
duk_small_uint_t i;
/* XXX: This is VERY inefficient now, and should be e.g. a
* binary search or perfect hash, to be fixed.
*/
for (i = 0; i < (duk_small_uint_t) (sizeof(duk_rom_strings) / sizeof(duk_hstring *)); i++) {
duk_hstring *romstr;
romstr = (duk_hstring *) DUK_LOSE_CONST(duk_rom_strings[i]);
if (blen == DUK_HSTRING_GET_BYTELEN(romstr) &&
DUK_MEMCMP((const void *) str, (const void *) DUK_HSTRING_GET_DATA(romstr), blen) == 0) {
DUK_DD(DUK_DDPRINT("intern check: rom string: %!O, computed hash 0x%08lx, rom hash 0x%08lx",
romstr, (unsigned long) *out_strhash, (unsigned long) DUK_HSTRING_GET_HASH(romstr)));
DUK_ASSERT(*out_strhash == DUK_HSTRING_GET_HASH(romstr));
*out_strhash = DUK_HSTRING_GET_HASH(romstr);
return romstr;
}
}
}
#endif /* DUK_USE_ROM_STRINGS */
#if defined(DUK_USE_STRTAB_CHAIN)
res = duk__find_matching_string_chain(heap, str, blen, *out_strhash);
#elif defined(DUK_USE_STRTAB_PROBE)
res = duk__find_matching_string_probe(heap,
#if defined(DUK_USE_HEAPPTR16)
heap->strtable16,
#else
heap->strtable,
#endif
heap->st_size,
str,
blen,
*out_strhash);
#else
#error internal error, invalid strtab options
#endif
return res;
}
/*
* Exposed calls
*/
#if 0 /*unused*/
DUK_INTERNAL duk_hstring *duk_heap_string_lookup(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
duk_uint32_t strhash; /* dummy */
return duk__do_lookup(heap, str, blen, &strhash);
}
#endif
DUK_INTERNAL duk_hstring *duk_heap_string_intern(duk_heap *heap, const duk_uint8_t *str, duk_uint32_t blen) {
duk_hstring *res;
duk_uint32_t strhash;
/* caller is responsible for ensuring this */
DUK_ASSERT(blen <= DUK_HSTRING_MAX_BYTELEN);
res = duk__do_lookup(heap, str, blen, &strhash);
if (res) {
return res;
}
res = duk__do_intern(heap, str, blen, strhash);
return res; /* may be NULL */
}
DUK_INTERNAL duk_hstring *duk_heap_string_intern_checked(duk_hthread *thr, const duk_uint8_t *str, duk_uint32_t blen) {
duk_hstring *res = duk_heap_string_intern(thr->heap, str, blen);
if (!res) {
DUK_ERROR_ALLOC_DEFMSG(thr);
}
return res;
}
#if 0 /*unused*/
DUK_INTERNAL duk_hstring *duk_heap_string_lookup_u32(duk_heap *heap, duk_uint32_t val) {
char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
buf[sizeof(buf) - 1] = (char) 0;
DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX); /* formatted result limited */
return duk_heap_string_lookup(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
}
#endif
DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32(duk_heap *heap, duk_uint32_t val) {
char buf[DUK_STRTAB_U32_MAX_STRLEN+1];
DUK_SNPRINTF(buf, sizeof(buf), "%lu", (unsigned long) val);
buf[sizeof(buf) - 1] = (char) 0;
DUK_ASSERT(DUK_STRLEN(buf) <= DUK_UINT32_MAX); /* formatted result limited */
return duk_heap_string_intern(heap, (const duk_uint8_t *) buf, (duk_uint32_t) DUK_STRLEN(buf));
}
DUK_INTERNAL duk_hstring *duk_heap_string_intern_u32_checked(duk_hthread *thr, duk_uint32_t val) {
duk_hstring *res = duk_heap_string_intern_u32(thr->heap, val);
if (!res) {
DUK_ERROR_ALLOC_DEFMSG(thr);
}
return res;
}
/* find and remove string from stringtable; caller must free the string itself */
#if defined(DUK_USE_REFERENCE_COUNTING)
DUK_INTERNAL void duk_heap_string_remove(duk_heap *heap, duk_hstring *h) {
DUK_DDD(DUK_DDDPRINT("remove string from stringtable: %!O", (duk_heaphdr *) h));
#if defined(DUK_USE_STRTAB_CHAIN)
duk__remove_matching_hstring_chain(heap, h);
#elif defined(DUK_USE_STRTAB_PROBE)
duk__remove_matching_hstring_probe(heap,
#if defined(DUK_USE_HEAPPTR16)
heap->strtable16,
#else
heap->strtable,
#endif
heap->st_size,
h);
#else
#error internal error, invalid strtab options
#endif
}
#endif
#if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_MS_STRINGTABLE_RESIZE)
DUK_INTERNAL void duk_heap_force_strtab_resize(duk_heap *heap) {
duk_small_uint_t prev_mark_and_sweep_base_flags;
/* Force a resize so that DELETED entries are eliminated.
* Another option would be duk__recheck_strtab_size_probe();
* but since that happens on every intern anyway, this whole
* check can now be disabled.
*/
DUK_ASSERT((heap->mark_and_sweep_base_flags & DUK_MS_FLAG_NO_STRINGTABLE_RESIZE) == 0);
prev_mark_and_sweep_base_flags = heap->mark_and_sweep_base_flags;
DUK__PREVENT_MS_SIDE_EFFECTS(heap);
#if defined(DUK_USE_STRTAB_CHAIN)
DUK_UNREF(heap);
#elif defined(DUK_USE_STRTAB_PROBE)
(void) duk__resize_strtab_probe(heap);
#endif
heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
}
#endif
#if defined(DUK_USE_STRTAB_CHAIN)
DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
/* Free strings in the stringtable and any allocations needed
* by the stringtable itself.
*/
duk_uint_fast32_t i, j;
duk_strtab_entry *e;
#if defined(DUK_USE_HEAPPTR16)
duk_uint16_t *lst;
duk_uint16_t null16 = heap->heapptr_null16;
#else
duk_hstring **lst;
#endif
duk_hstring *h;
for (i = 0; i < DUK_STRTAB_CHAIN_SIZE; i++) {
e = heap->strtable + i;
if (e->listlen > 0) {
#if defined(DUK_USE_HEAPPTR16)
lst = (duk_uint16_t *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.strlist16);
#else
lst = e->u.strlist;
#endif
DUK_ASSERT(lst != NULL);
for (j = 0; j < e->listlen; j++) {
#if defined(DUK_USE_HEAPPTR16)
h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, lst[j]);
lst[j] = null16;
#else
h = lst[j];
lst[j] = NULL;
#endif
/* strings may have inner refs (extdata) in some cases */
if (h != NULL) {
duk_free_hstring_inner(heap, h);
DUK_FREE(heap, h);
}
}
#if defined(DUK_USE_HEAPPTR16)
e->u.strlist16 = null16;
#else
e->u.strlist = NULL;
#endif
DUK_FREE(heap, lst);
} else {
#if defined(DUK_USE_HEAPPTR16)
h = DUK_USE_HEAPPTR_DEC16(heap->heap_udata, e->u.str16);
e->u.str16 = null16;
#else
h = e->u.str;
e->u.str = NULL;
#endif
if (h != NULL) {
duk_free_hstring_inner(heap, h);
DUK_FREE(heap, h);
}
}
e->listlen = 0;
}
}
#endif /* DUK_USE_STRTAB_CHAIN */
#if defined(DUK_USE_STRTAB_PROBE)
DUK_INTERNAL void duk_heap_free_strtab(duk_heap *heap) {
duk_uint_fast32_t i;
duk_hstring *h;
#if defined(DUK_USE_HEAPPTR16)
if (heap->strtable16) {
#else
if (heap->strtable) {
#endif
for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) {
#if defined(DUK_USE_HEAPPTR16)
h = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->heap_udata, heap->strtable16[i]);
#else
h = heap->strtable[i];
#endif
if (h == NULL || h == DUK_STRTAB_DELETED_MARKER(heap)) {
continue;
}
DUK_ASSERT(h != NULL);
/* strings may have inner refs (extdata) in some cases */
duk_free_hstring_inner(heap, h);
DUK_FREE(heap, h);
#if 0 /* not strictly necessary */
heap->strtable[i] = NULL;
#endif
}
#if defined(DUK_USE_HEAPPTR16)
DUK_FREE(heap, heap->strtable16);
#else
DUK_FREE(heap, heap->strtable);
#endif
#if 0 /* not strictly necessary */
heap->strtable = NULL;
#endif
}
}
#endif /* DUK_USE_STRTAB_PROBE */
/* Undefine local defines */
#undef DUK__HASH_INITIAL
#undef DUK__HASH_PROBE_STEP
#undef DUK__DELETED_MARKER