| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * String interning. |
| */ |
| #include "Dalvik.h" |
| |
| #include <stdlib.h> |
| |
| #define INTERN_STRING_IMMORTAL_BIT (1<<0) |
| #define SET_IMMORTAL_BIT(strObj) \ |
| ((uintptr_t)(strObj) | INTERN_STRING_IMMORTAL_BIT) |
| #define STRIP_IMMORTAL_BIT(strObj) \ |
| ((uintptr_t)(strObj) & ~INTERN_STRING_IMMORTAL_BIT) |
| #define IS_IMMORTAL(strObj) \ |
| ((uintptr_t)(strObj) & INTERN_STRING_IMMORTAL_BIT) |
| |
| |
| /* |
| * Prep string interning. |
| */ |
| bool dvmStringInternStartup(void) |
| { |
| gDvm.internedStrings = dvmHashTableCreate(256, NULL); |
| if (gDvm.internedStrings == NULL) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * Chuck the intern list. |
| * |
| * The contents of the list are StringObjects that live on the GC heap. |
| */ |
| void dvmStringInternShutdown(void) |
| { |
| dvmHashTableFree(gDvm.internedStrings); |
| gDvm.internedStrings = NULL; |
| } |
| |
| |
| /* |
| * Compare two string objects that may have INTERN_STRING_IMMORTAL_BIT |
| * set in their pointer values. |
| */ |
| static int hashcmpImmortalStrings(const void* vstrObj1, const void* vstrObj2) |
| { |
| return dvmHashcmpStrings((const void*) STRIP_IMMORTAL_BIT(vstrObj1), |
| (const void*) STRIP_IMMORTAL_BIT(vstrObj2)); |
| } |
| |
| static StringObject* lookupInternedString(StringObject* strObj, bool immortal) |
| { |
| StringObject* found; |
| u4 hash; |
| |
| assert(strObj != NULL); |
| hash = dvmComputeStringHash(strObj); |
| |
| if (false) { |
| char* debugStr = dvmCreateCstrFromString(strObj); |
| LOGV("+++ dvmLookupInternedString searching for '%s'\n", debugStr); |
| free(debugStr); |
| } |
| |
| if (immortal) { |
| strObj = (StringObject*) SET_IMMORTAL_BIT(strObj); |
| } |
| |
| dvmHashTableLock(gDvm.internedStrings); |
| |
| found = (StringObject*) dvmHashTableLookup(gDvm.internedStrings, |
| hash, strObj, hashcmpImmortalStrings, true); |
| if (immortal && !IS_IMMORTAL(found)) { |
| /* Make this entry immortal. We have to use the existing object |
| * because, as an interned string, it's not allowed to change. |
| * |
| * There's no way to get a pointer to the actual hash table entry, |
| * so the only way to modify the existing entry is to remove, |
| * modify, and re-add it. |
| */ |
| dvmHashTableRemove(gDvm.internedStrings, hash, found); |
| found = (StringObject*) SET_IMMORTAL_BIT(found); |
| found = (StringObject*) dvmHashTableLookup(gDvm.internedStrings, |
| hash, found, hashcmpImmortalStrings, true); |
| assert(IS_IMMORTAL(found)); |
| } |
| |
| dvmHashTableUnlock(gDvm.internedStrings); |
| |
| //if (found == strObj) |
| // LOGVV("+++ added string\n"); |
| return (StringObject*) STRIP_IMMORTAL_BIT(found); |
| } |
| |
| /* |
| * Find an entry in the interned string list. |
| * |
| * If the string doesn't already exist, the StringObject is added to |
| * the list. Otherwise, the existing entry is returned. |
| */ |
| StringObject* dvmLookupInternedString(StringObject* strObj) |
| { |
| return lookupInternedString(strObj, false); |
| } |
| |
| /* |
| * Same as dvmLookupInternedString(), but guarantees that the |
| * returned string is immortal. |
| */ |
| StringObject* dvmLookupImmortalInternedString(StringObject* strObj) |
| { |
| return lookupInternedString(strObj, true); |
| } |
| |
| /* |
| * Mark all immortal interned string objects so that they don't |
| * get collected by the GC. Non-immortal strings may or may not |
| * get marked by other references. |
| */ |
| static int markStringObject(void* strObj, void* arg) |
| { |
| UNUSED_PARAMETER(arg); |
| |
| if (IS_IMMORTAL(strObj)) { |
| dvmMarkObjectNonNull((Object*) STRIP_IMMORTAL_BIT(strObj)); |
| } |
| return 0; |
| } |
| |
| void dvmGcScanInternedStrings() |
| { |
| /* It's possible for a GC to happen before dvmStringInternStartup() |
| * is called. |
| */ |
| if (gDvm.internedStrings != NULL) { |
| dvmHashTableLock(gDvm.internedStrings); |
| dvmHashForeach(gDvm.internedStrings, markStringObject, NULL); |
| dvmHashTableUnlock(gDvm.internedStrings); |
| } |
| } |
| |
| /* |
| * Called by the GC after all reachable objects have been |
| * marked. isUnmarkedObject is a function suitable for passing |
| * to dvmHashForeachRemove(); it must strip the low bits from |
| * its pointer argument to deal with the immortal bit, though. |
| */ |
| void dvmGcDetachDeadInternedStrings(int (*isUnmarkedObject)(void *)) |
| { |
| /* It's possible for a GC to happen before dvmStringInternStartup() |
| * is called. |
| */ |
| if (gDvm.internedStrings != NULL) { |
| dvmHashTableLock(gDvm.internedStrings); |
| dvmHashForeachRemove(gDvm.internedStrings, isUnmarkedObject); |
| dvmHashTableUnlock(gDvm.internedStrings); |
| } |
| } |