/*
* 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.
*/
/*
* Mutex-free cache. Each entry has two 32-bit keys, one 32-bit value,
* and a 32-bit version.
*/
#include "Dalvik.h"
#include <stdlib.h>
/*
* I think modern C mandates that the results of a boolean expression are
* 0 or 1. If not, or we suddenly turn into C++ and bool != int, use this.
*/
#define BOOL_TO_INT(x) (x)
//#define BOOL_TO_INT(x) ((x) ? 1 : 0)
#define CPU_CACHE_WIDTH 32
#define CPU_CACHE_WIDTH_1 (CPU_CACHE_WIDTH-1)
#define ATOMIC_LOCK_FLAG (1 << 31)
/*
* Allocate cache.
*/
AtomicCache* dvmAllocAtomicCache(int numEntries)
{
AtomicCache* newCache;
newCache = (AtomicCache*) calloc(1, sizeof(AtomicCache));
if (newCache == NULL)
return NULL;
newCache->numEntries = numEntries;
newCache->entryAlloc = calloc(1,
sizeof(AtomicCacheEntry) * numEntries + CPU_CACHE_WIDTH);
if (newCache->entryAlloc == NULL)
return NULL;
/*
* Adjust storage to align on a 32-byte boundary. Each entry is 16 bytes
* wide. This ensures that each cache entry sits on a single CPU cache
* line.
*/
assert(sizeof(AtomicCacheEntry) == 16);
newCache->entries = (AtomicCacheEntry*)
(((int) newCache->entryAlloc + CPU_CACHE_WIDTH_1) & ~CPU_CACHE_WIDTH_1);
return newCache;
}
/*
* Free cache.
*/
void dvmFreeAtomicCache(AtomicCache* cache)
{
if (cache != NULL) {
free(cache->entryAlloc);
free(cache);
}
}
/*
* Update a cache entry.
*
* In the event of a collision with another thread, the update may be skipped.
*
* We only need "pCache" for stats.
*/
void dvmUpdateAtomicCache(u4 key1, u4 key2, u4 value, AtomicCacheEntry* pEntry,
u4 firstVersion
#if CALC_CACHE_STATS > 0
, AtomicCache* pCache
#endif
)
{
/*
* The fields don't match, so we need to update them. There is a
* risk that another thread is also trying to update them, so we
* grab an ownership flag to lock out other threads.
*
* If the lock flag was already set in "firstVersion", somebody else
* was in mid-update. (This means that using "firstVersion" as the
* "before" argument to the CAS would succeed when it shouldn't and
* vice-versa -- we could also just pass in
* (firstVersion & ~ATOMIC_LOCK_FLAG) as the first argument.)
*
* NOTE: we don't really deal with the situation where we overflow
* the version counter (at 2^31). Probably not a real concern.
*/
if ((firstVersion & ATOMIC_LOCK_FLAG) != 0 ||
!ATOMIC_CMP_SWAP((volatile s4*) &pEntry->version,
firstVersion, firstVersion | ATOMIC_LOCK_FLAG))
{
/*
* We couldn't get the write lock. Return without updating the table.
*/
#if CALC_CACHE_STATS > 0
pCache->fail++;
#endif
return;
}
/* must be even-valued on entry */
assert((firstVersion & 0x01) == 0);
#if CALC_CACHE_STATS > 0
/* for stats, assume a key value of zero indicates an empty entry */
if (pEntry->key1 == 0)
pCache->fills++;
else
pCache->misses++;
#endif
/* volatile incr */
pEntry->version++;
MEM_BARRIER();
pEntry->key1 = key1;
pEntry->key2 = key2;
pEntry->value = value;
/* volatile incr */
pEntry->version++;
MEM_BARRIER();
/*
* Clear the lock flag. Nobody else should have been able to modify
* pEntry->version, so if this fails the world is broken.
*/
firstVersion += 2;
if (!ATOMIC_CMP_SWAP((volatile s4*) &pEntry->version,
firstVersion | ATOMIC_LOCK_FLAG, firstVersion))
{
//LOGE("unable to reset the instanceof cache ownership\n");
dvmAbort();
}
}
/*
* Dump the "instanceof" cache stats.
*/
void dvmDumpAtomicCacheStats(const AtomicCache* pCache)
{
if (pCache == NULL)
return;
dvmFprintf(stdout,
"Cache stats: trv=%d fai=%d hit=%d mis=%d fil=%d %d%% (size=%d)\n",
pCache->trivial, pCache->fail, pCache->hits,
pCache->misses, pCache->fills,
(pCache->hits == 0) ? 0 :
pCache->hits * 100 /
(pCache->fail + pCache->hits + pCache->misses + pCache->fills),
pCache->numEntries);
}