/* +----------------------------------------------------------------------+ | Zend Engine | +----------------------------------------------------------------------+ | Copyright (c) 1998-2016 Zend Technologies Ltd. (http://www.zend.com) | +----------------------------------------------------------------------+ | This source file is subject to version 2.00 of the Zend license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.zend.com/license/2_00.txt. | | If you did not receive a copy of the Zend license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@zend.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Dmitry Stogov | +----------------------------------------------------------------------+ */ /* $Id: $ */ #include "zend.h" #include "zend_globals.h" ZEND_API zend_string *(*zend_new_interned_string)(zend_string *str); ZEND_API void (*zend_interned_strings_snapshot)(void); ZEND_API void (*zend_interned_strings_restore)(void); static zend_string *zend_new_interned_string_int(zend_string *str); static void zend_interned_strings_snapshot_int(void); static void zend_interned_strings_restore_int(void); ZEND_API zend_ulong zend_hash_func(const char *str, size_t len) { return zend_inline_hash_func(str, len); } #ifndef ZTS static void _str_dtor(zval *zv) { zend_string *str = Z_STR_P(zv); pefree(str, GC_FLAGS(str) & IS_STR_PERSISTENT); } #endif /* Readonly, so assigned also per thread. */ static const zend_string **known_interned_strings = NULL; static uint32_t known_interned_strings_count = 0; ZEND_API uint32_t zend_intern_known_strings(const char **strings, uint32_t count) { uint32_t i, old_count = known_interned_strings_count; known_interned_strings = perealloc(known_interned_strings, sizeof(char*) * (old_count + count), 1); for (i = 0; i < count; i++) { #ifndef ZTS zend_string *str = zend_string_init(strings[i], strlen(strings[i]), 1); known_interned_strings[known_interned_strings_count + i] = zend_new_interned_string_int(str); #else known_interned_strings[known_interned_strings_count + i] = zend_zts_interned_string_init(strings[i], strlen(strings[i])); #endif } known_interned_strings_count = old_count + count; return old_count; } static const char *known_strings[] = { #define _ZEND_STR_DSC(id, str) str, ZEND_KNOWN_STRINGS(_ZEND_STR_DSC) #undef _ZEND_STR_DSC NULL }; void zend_known_interned_strings_init(zend_string ***strings, uint32_t *count) { *strings = (zend_string **)known_interned_strings; *count = known_interned_strings_count; } void zend_interned_strings_init(void) { #ifndef ZTS zend_string *str; zend_hash_init(&CG(interned_strings), 1024, NULL, _str_dtor, 1); CG(interned_strings).nTableMask = -CG(interned_strings).nTableSize; HT_SET_DATA_ADDR(&CG(interned_strings), pemalloc(HT_SIZE(&CG(interned_strings)), 1)); HT_HASH_RESET(&CG(interned_strings)); CG(interned_strings).u.flags |= HASH_FLAG_INITIALIZED; /* interned empty string */ str = zend_string_alloc(sizeof("")-1, 1); ZSTR_VAL(str)[0] = '\000'; CG(empty_string) = zend_new_interned_string_int(str); #endif /* one char strings (the actual interned strings are going to be created by ext/opcache) */ memset(CG(one_char_string), 0, sizeof(CG(one_char_string))); /* known strings */ zend_intern_known_strings(known_strings, (sizeof(known_strings) / sizeof(known_strings[0])) - 1); zend_known_interned_strings_init(&CG(known_strings), &CG(known_strings_count)); zend_new_interned_string = zend_new_interned_string_int; zend_interned_strings_snapshot = zend_interned_strings_snapshot_int; zend_interned_strings_restore = zend_interned_strings_restore_int; } void zend_interned_strings_dtor(void) { #ifndef ZTS zend_hash_destroy(&CG(interned_strings)); #else uint32_t i; for (i = 0; i < CG(known_strings_count); i++) { zend_zts_interned_string_free(&CG(known_strings)[i]); } #endif free(CG(known_strings)); CG(known_strings) = NULL; CG(known_strings_count) = 0; known_interned_strings = NULL; known_interned_strings_count = 0; } static zend_string *zend_new_interned_string_int(zend_string *str) { #ifndef ZTS zend_ulong h; uint nIndex; uint idx; Bucket *p; if (ZSTR_IS_INTERNED(str)) { return str; } h = zend_string_hash_val(str); nIndex = h | CG(interned_strings).nTableMask; idx = HT_HASH(&CG(interned_strings), nIndex); while (idx != HT_INVALID_IDX) { p = HT_HASH_TO_BUCKET(&CG(interned_strings), idx); if ((p->h == h) && (ZSTR_LEN(p->key) == ZSTR_LEN(str))) { if (!memcmp(ZSTR_VAL(p->key), ZSTR_VAL(str), ZSTR_LEN(str))) { zend_string_release(str); return p->key; } } idx = Z_NEXT(p->val); } GC_REFCOUNT(str) = 1; GC_FLAGS(str) |= IS_STR_INTERNED; if (CG(interned_strings).nNumUsed >= CG(interned_strings).nTableSize) { if (CG(interned_strings).nTableSize < HT_MAX_SIZE) { /* Let's double the table size */ void *new_data; void *old_data = HT_GET_DATA_ADDR(&CG(interned_strings)); Bucket *old_buckets = CG(interned_strings).arData; CG(interned_strings).nTableSize += CG(interned_strings).nTableSize; CG(interned_strings).nTableMask = -CG(interned_strings).nTableSize; new_data = malloc(HT_SIZE(&CG(interned_strings))); if (new_data) { HT_SET_DATA_ADDR(&CG(interned_strings), new_data); memcpy(CG(interned_strings).arData, old_buckets, sizeof(Bucket) * CG(interned_strings).nNumUsed); free(old_data); zend_hash_rehash(&CG(interned_strings)); } else { CG(interned_strings).nTableSize = CG(interned_strings).nTableSize >> 1; CG(interned_strings).nTableMask = -CG(interned_strings).nTableSize; } } } idx = CG(interned_strings).nNumUsed++; CG(interned_strings).nNumOfElements++; p = CG(interned_strings).arData + idx; p->h = h; p->key = str; Z_STR(p->val) = str; Z_TYPE_INFO(p->val) = IS_INTERNED_STRING_EX; nIndex = h | CG(interned_strings).nTableMask; Z_NEXT(p->val) = HT_HASH(&CG(interned_strings), nIndex); HT_HASH(&CG(interned_strings), nIndex) = HT_IDX_TO_HASH(idx); return str; #else return str; #endif } static void zend_interned_strings_snapshot_int(void) { #ifndef ZTS uint idx; Bucket *p; idx = CG(interned_strings).nNumUsed; while (idx > 0) { idx--; p = CG(interned_strings).arData + idx; ZEND_ASSERT(GC_FLAGS(p->key) & IS_STR_PERSISTENT); GC_FLAGS(p->key) |= IS_STR_PERMANENT; } #endif } static void zend_interned_strings_restore_int(void) { #ifndef ZTS uint nIndex; uint idx; Bucket *p; idx = CG(interned_strings).nNumUsed; while (idx > 0) { idx--; p = CG(interned_strings).arData + idx; if (GC_FLAGS(p->key) & IS_STR_PERMANENT) break; CG(interned_strings).nNumUsed--; CG(interned_strings).nNumOfElements--; GC_FLAGS(p->key) &= ~IS_STR_INTERNED; GC_REFCOUNT(p->key) = 1; zend_string_free(p->key); nIndex = p->h | CG(interned_strings).nTableMask; if (HT_HASH(&CG(interned_strings), nIndex) == HT_IDX_TO_HASH(idx)) { HT_HASH(&CG(interned_strings), nIndex) = Z_NEXT(p->val); } else { uint32_t prev = HT_HASH(&CG(interned_strings), nIndex); while (Z_NEXT(HT_HASH_TO_BUCKET(&CG(interned_strings), prev)->val) != idx) { prev = Z_NEXT(HT_HASH_TO_BUCKET(&CG(interned_strings), prev)->val); } Z_NEXT(HT_HASH_TO_BUCKET(&CG(interned_strings), prev)->val) = Z_NEXT(p->val); } } #endif } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * indent-tabs-mode: t * End: */