blob: 1da4917ddefce3d6903f835ad7a54b7112f52d9d [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "property_map.h"
#include "object.h"
#include "protect.h"
#include "PropertyNameArray.h"
#include <algorithm>
#include <wtf/Assertions.h>
#include <wtf/FastMalloc.h>
#include <wtf/HashTable.h>
#include <wtf/Vector.h>
using std::max;
using WTF::doubleHash;
#ifndef NDEBUG
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
#define DUMP_PROPERTYMAP_STATS 0
#else
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
#define DUMP_PROPERTYMAP_STATS 0
#endif
#define USE_SINGLE_ENTRY 1
// 2/28/2006 ggaren: command-line JS iBench says that USE_SINGLE_ENTRY is a
// 3.2% performance boost.
namespace KJS {
// Choose a number for the following so that most property maps are smaller,
// but it's not going to blow out the stack to allocate this number of pointers.
static const int smallMapThreshold = 1024;
// The point at which the function call overhead of the qsort implementation
// becomes small compared to the inefficiency of insertion sort.
static const unsigned tinyMapThreshold = 20;
#if DUMP_PROPERTYMAP_STATS
static int numProbes;
static int numCollisions;
static int numRehashes;
static int numRemoves;
struct PropertyMapStatisticsExitLogger { ~PropertyMapStatisticsExitLogger(); };
static PropertyMapStatisticsExitLogger logger;
PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger()
{
printf("\nKJS::PropertyMap statistics\n\n");
printf("%d probes\n", numProbes);
printf("%d collisions (%.1f%%)\n", numCollisions, 100.0 * numCollisions / numProbes);
printf("%d rehashes\n", numRehashes);
printf("%d removes\n", numRemoves);
}
#endif
struct PropertyMapEntry {
UString::Rep* key;
JSValue* value;
unsigned attributes;
unsigned index;
PropertyMapEntry(UString::Rep* k, JSValue* v, int a)
: key(k), value(v), attributes(a), index(0)
{
}
};
// lastIndexUsed is an ever-increasing index used to identify the order items
// were inserted into the property map. It's required that getEnumerablePropertyNames
// return the properties in the order they were added for compatibility with other
// browsers' JavaScript implementations.
struct PropertyMapHashTable {
unsigned sizeMask;
unsigned size;
unsigned keyCount;
unsigned deletedSentinelCount;
unsigned lastIndexUsed;
unsigned entryIndicies[1];
PropertyMapEntry* entries()
{
// The entries vector comes after the indices vector.
// The 0th item in the entries vector is not really used; it has to
// have a 0 in its key to allow the hash table lookup to handle deleted
// sentinels without any special-case code, but the other fields are unused.
return reinterpret_cast<PropertyMapEntry*>(&entryIndicies[size]);
}
static size_t allocationSize(unsigned size)
{
// We never let a hash table get more than half full,
// So the number of indices we need is the size of the hash table.
// But the number of entries is half that (plus one for the deleted sentinel).
return sizeof(PropertyMapHashTable)
+ (size - 1) * sizeof(unsigned)
+ (1 + size / 2) * sizeof(PropertyMapEntry);
}
};
static const unsigned emptyEntryIndex = 0;
static const unsigned deletedSentinelIndex = 1;
SavedProperties::SavedProperties()
: count(0)
{
}
SavedProperties::~SavedProperties()
{
}
#if !DO_PROPERTYMAP_CONSTENCY_CHECK
inline void PropertyMap::checkConsistency()
{
}
#endif
PropertyMap::~PropertyMap()
{
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (m_singleEntryKey)
m_singleEntryKey->deref();
#endif
return;
}
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; i++) {
if (UString::Rep* key = m_u.table->entries()[i].key)
key->deref();
}
fastFree(m_u.table);
}
void PropertyMap::clear()
{
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (m_singleEntryKey) {
m_singleEntryKey->deref();
m_singleEntryKey = 0;
}
#endif
return;
}
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; i++) {
if (UString::Rep* key = m_u.table->entries()[i].key)
key->deref();
}
for (unsigned i = 0; i < m_u.table->size; i++)
m_u.table->entryIndicies[i] = emptyEntryIndex;
m_u.table->keyCount = 0;
m_u.table->deletedSentinelCount = 0;
}
JSValue* PropertyMap::get(const Identifier& name, unsigned& attributes) const
{
ASSERT(!name.isNull());
UString::Rep* rep = name._ustring.rep();
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (rep == m_singleEntryKey) {
attributes = m_singleEntryAttributes;
return m_u.singleEntryValue;
}
#endif
return 0;
}
unsigned i = rep->computedHash();
#if DUMP_PROPERTYMAP_STATS
++numProbes;
#endif
unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key) {
attributes = m_u.table->entries()[entryIndex - 1].attributes;
return m_u.table->entries()[entryIndex - 1].value;
}
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
unsigned k = 1 | doubleHash(rep->computedHash());
while (1) {
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key) {
attributes = m_u.table->entries()[entryIndex - 1].attributes;
return m_u.table->entries()[entryIndex - 1].value;
}
}
}
JSValue* PropertyMap::get(const Identifier& name) const
{
ASSERT(!name.isNull());
UString::Rep* rep = name._ustring.rep();
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (rep == m_singleEntryKey)
return m_u.singleEntryValue;
#endif
return 0;
}
unsigned i = rep->computedHash();
#if DUMP_PROPERTYMAP_STATS
++numProbes;
#endif
unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key)
return m_u.table->entries()[entryIndex - 1].value;
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
unsigned k = 1 | doubleHash(rep->computedHash());
while (1) {
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key)
return m_u.table->entries()[entryIndex - 1].value;
}
}
JSValue** PropertyMap::getLocation(const Identifier& name)
{
ASSERT(!name.isNull());
UString::Rep* rep = name._ustring.rep();
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (rep == m_singleEntryKey)
return &m_u.singleEntryValue;
#endif
return 0;
}
unsigned i = rep->computedHash();
#if DUMP_PROPERTYMAP_STATS
++numProbes;
#endif
unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key)
return &m_u.table->entries()[entryIndex - 1].value;
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
unsigned k = 1 | doubleHash(rep->computedHash());
while (1) {
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return 0;
if (rep == m_u.table->entries()[entryIndex - 1].key)
return &m_u.table->entries()[entryIndex - 1].value;
}
}
void PropertyMap::put(const Identifier& name, JSValue* value, unsigned attributes, bool checkReadOnly)
{
ASSERT(!name.isNull());
ASSERT(value);
checkConsistency();
UString::Rep* rep = name._ustring.rep();
#if USE_SINGLE_ENTRY
if (!m_usingTable) {
if (!m_singleEntryKey) {
rep->ref();
m_singleEntryKey = rep;
m_u.singleEntryValue = value;
m_singleEntryAttributes = static_cast<short>(attributes);
checkConsistency();
return;
}
if (rep == m_singleEntryKey && !(checkReadOnly && (m_singleEntryAttributes & ReadOnly))) {
m_u.singleEntryValue = value;
return;
}
}
#endif
if (!m_usingTable || (m_u.table->keyCount + m_u.table->deletedSentinelCount) * 2 >= m_u.table->size)
expand();
// FIXME: Consider a fast case for tables with no deleted sentinels.
unsigned i = rep->computedHash();
unsigned k = 0;
bool foundDeletedElement = false;
unsigned deletedElementIndex = 0; // initialize to make the compiler happy
#if DUMP_PROPERTYMAP_STATS
++numProbes;
#endif
while (1) {
unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
break;
if (m_u.table->entries()[entryIndex - 1].key == rep) {
if (checkReadOnly && (m_u.table->entries()[entryIndex - 1].attributes & ReadOnly))
return;
// Put a new value in an existing hash table entry.
m_u.table->entries()[entryIndex - 1].value = value;
// Attributes are intentionally not updated.
return;
} else if (entryIndex == deletedSentinelIndex) {
// If we find a deleted-element sentinel, remember it for use later.
if (!foundDeletedElement) {
foundDeletedElement = true;
deletedElementIndex = i;
}
}
if (k == 0) {
k = 1 | doubleHash(rep->computedHash());
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
}
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
}
// Figure out which entry to use.
unsigned entryIndex = m_u.table->keyCount + m_u.table->deletedSentinelCount + 2;
if (foundDeletedElement) {
i = deletedElementIndex;
--m_u.table->deletedSentinelCount;
// Since we're not making the table bigger, we can't use the entry one past
// the end that we were planning on using, so search backwards for the empty
// slot that we can use. We know it will be there because we did at least one
// deletion in the past that left an entry empty.
while (m_u.table->entries()[--entryIndex - 1].key)
;
}
// Create a new hash table entry.
m_u.table->entryIndicies[i & m_u.table->sizeMask] = entryIndex;
// Create a new hash table entry.
rep->ref();
m_u.table->entries()[entryIndex - 1].key = rep;
m_u.table->entries()[entryIndex - 1].value = value;
m_u.table->entries()[entryIndex - 1].attributes = attributes;
m_u.table->entries()[entryIndex - 1].index = ++m_u.table->lastIndexUsed;
++m_u.table->keyCount;
checkConsistency();
}
void PropertyMap::insert(const Entry& entry)
{
ASSERT(m_u.table);
unsigned i = entry.key->computedHash();
unsigned k = 0;
#if DUMP_PROPERTYMAP_STATS
++numProbes;
#endif
while (1) {
unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
break;
if (k == 0) {
k = 1 | doubleHash(entry.key->computedHash());
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
}
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
}
unsigned entryIndex = m_u.table->keyCount + 2;
m_u.table->entryIndicies[i & m_u.table->sizeMask] = entryIndex;
m_u.table->entries()[entryIndex - 1] = entry;
++m_u.table->keyCount;
}
void PropertyMap::expand()
{
if (!m_usingTable)
createTable();
else
rehash(m_u.table->size * 2);
}
void PropertyMap::rehash()
{
ASSERT(m_usingTable);
ASSERT(m_u.table);
ASSERT(m_u.table->size);
rehash(m_u.table->size);
}
void PropertyMap::createTable()
{
const unsigned newTableSize = 16;
ASSERT(!m_usingTable);
checkConsistency();
#if USE_SINGLE_ENTRY
JSValue* oldSingleEntryValue = m_u.singleEntryValue;
#endif
m_u.table = static_cast<Table*>(fastZeroedMalloc(Table::allocationSize(newTableSize)));
m_u.table->size = newTableSize;
m_u.table->sizeMask = newTableSize - 1;
m_usingTable = true;
#if USE_SINGLE_ENTRY
if (m_singleEntryKey) {
insert(Entry(m_singleEntryKey, oldSingleEntryValue, m_singleEntryAttributes));
m_singleEntryKey = 0;
}
#endif
checkConsistency();
}
void PropertyMap::rehash(unsigned newTableSize)
{
ASSERT(!m_singleEntryKey);
ASSERT(m_u.table);
ASSERT(m_usingTable);
checkConsistency();
Table* oldTable = m_u.table;
m_u.table = static_cast<Table*>(fastZeroedMalloc(Table::allocationSize(newTableSize)));
m_u.table->size = newTableSize;
m_u.table->sizeMask = newTableSize - 1;
unsigned lastIndexUsed = 0;
unsigned entryCount = oldTable->keyCount + oldTable->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; ++i) {
if (oldTable->entries()[i].key) {
lastIndexUsed = max(oldTable->entries()[i].index, lastIndexUsed);
insert(oldTable->entries()[i]);
}
}
m_u.table->lastIndexUsed = lastIndexUsed;
fastFree(oldTable);
checkConsistency();
}
void PropertyMap::remove(const Identifier& name)
{
ASSERT(!name.isNull());
checkConsistency();
UString::Rep* rep = name._ustring.rep();
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (rep == m_singleEntryKey) {
m_singleEntryKey->deref();
m_singleEntryKey = 0;
checkConsistency();
}
#endif
return;
}
#if DUMP_PROPERTYMAP_STATS
++numProbes;
++numRemoves;
#endif
// Find the thing to remove.
unsigned i = rep->computedHash();
unsigned k = 0;
unsigned entryIndex;
UString::Rep* key = 0;
while (1) {
entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
if (entryIndex == emptyEntryIndex)
return;
key = m_u.table->entries()[entryIndex - 1].key;
if (rep == key)
break;
if (k == 0) {
k = 1 | doubleHash(rep->computedHash());
#if DUMP_PROPERTYMAP_STATS
++numCollisions;
#endif
}
i += k;
#if DUMP_PROPERTYMAP_STATS
++numRehashes;
#endif
}
// Replace this one element with the deleted sentinel. Also clear out
// the entry so we can iterate all the entries as needed.
m_u.table->entryIndicies[i & m_u.table->sizeMask] = deletedSentinelIndex;
key->deref();
m_u.table->entries()[entryIndex - 1].key = 0;
m_u.table->entries()[entryIndex - 1].value = jsUndefined();
m_u.table->entries()[entryIndex - 1].attributes = 0;
ASSERT(m_u.table->keyCount >= 1);
--m_u.table->keyCount;
++m_u.table->deletedSentinelCount;
if (m_u.table->deletedSentinelCount * 4 >= m_u.table->size)
rehash();
checkConsistency();
}
void PropertyMap::mark() const
{
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (m_singleEntryKey) {
JSValue* v = m_u.singleEntryValue;
if (!v->marked())
v->mark();
}
#endif
return;
}
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; i++) {
JSValue* v = m_u.table->entries()[i].value;
if (!v->marked())
v->mark();
}
}
static int comparePropertyMapEntryIndices(const void* a, const void* b)
{
unsigned ia = static_cast<PropertyMapEntry* const*>(a)[0]->index;
unsigned ib = static_cast<PropertyMapEntry* const*>(b)[0]->index;
if (ia < ib)
return -1;
if (ia > ib)
return +1;
return 0;
}
bool PropertyMap::containsGettersOrSetters() const
{
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
return !!(m_singleEntryAttributes & GetterSetter);
#else
return false;
#endif
}
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; i++) {
if (m_u.table->entries()[i].attributes & GetterSetter)
return true;
}
return false;
}
void PropertyMap::getEnumerablePropertyNames(PropertyNameArray& propertyNames) const
{
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
UString::Rep* key = m_singleEntryKey;
if (key && !(m_singleEntryAttributes & DontEnum))
propertyNames.add(Identifier(key));
#endif
return;
}
if (m_u.table->keyCount < tinyMapThreshold) {
Entry* a[tinyMapThreshold];
int i = 0;
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned k = 1; k <= entryCount; k++) {
if (m_u.table->entries()[k].key && !(m_u.table->entries()[k].attributes & DontEnum)) {
Entry* value = &m_u.table->entries()[k];
int j;
for (j = i - 1; j >= 0 && a[j]->index > value->index; --j)
a[j + 1] = a[j];
a[j + 1] = value;
++i;
}
}
for (int k = 0; k < i; ++k)
propertyNames.add(Identifier(a[k]->key));
return;
}
// Allocate a buffer to use to sort the keys.
Vector<Entry*, smallMapThreshold> sortedEnumerables(m_u.table->keyCount);
// Get pointers to the enumerable entries in the buffer.
Entry** p = sortedEnumerables.data();
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; i++) {
if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & DontEnum))
*p++ = &m_u.table->entries()[i];
}
// Sort the entries by index.
qsort(sortedEnumerables.data(), p - sortedEnumerables.data(), sizeof(Entry*), comparePropertyMapEntryIndices);
// Put the keys of the sorted entries into the list.
for (Entry** q = sortedEnumerables.data(); q != p; ++q)
propertyNames.add(Identifier(q[0]->key));
}
void PropertyMap::save(SavedProperties& s) const
{
unsigned count = 0;
if (!m_usingTable) {
#if USE_SINGLE_ENTRY
if (m_singleEntryKey && !(m_singleEntryAttributes & (ReadOnly | Function)))
++count;
#endif
} else {
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; ++i)
if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & (ReadOnly | Function)))
++count;
}
s.properties.clear();
s.count = count;
if (count == 0)
return;
s.properties.set(new SavedProperty[count]);
SavedProperty* prop = s.properties.get();
#if USE_SINGLE_ENTRY
if (!m_usingTable) {
prop->init(m_singleEntryKey, m_u.singleEntryValue, m_singleEntryAttributes);
return;
}
#endif
// Save in the right order so we don't lose the order.
// Another possibility would be to save the indices.
// Allocate a buffer to use to sort the keys.
Vector<Entry*, smallMapThreshold> sortedEntries(count);
// Get pointers to the entries in the buffer.
Entry** p = sortedEntries.data();
unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
for (unsigned i = 1; i <= entryCount; ++i) {
if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & (ReadOnly | Function)))
*p++ = &m_u.table->entries()[i];
}
ASSERT(p == sortedEntries.data() + count);
// Sort the entries by index.
qsort(sortedEntries.data(), p - sortedEntries.data(), sizeof(Entry*), comparePropertyMapEntryIndices);
// Put the sorted entries into the saved properties list.
for (Entry** q = sortedEntries.data(); q != p; ++q, ++prop) {
Entry* e = *q;
prop->init(e->key, e->value, e->attributes);
}
}
void PropertyMap::restore(const SavedProperties& p)
{
for (unsigned i = 0; i != p.count; ++i)
put(Identifier(p.properties[i].name()), p.properties[i].value(), p.properties[i].attributes());
}
#if DO_PROPERTYMAP_CONSTENCY_CHECK
void PropertyMap::checkConsistency()
{
if (!m_usingTable)
return;
ASSERT(m_u.table->size >= 16);
ASSERT(m_u.table->sizeMask);
ASSERT(m_u.table->size == m_u.table->sizeMask + 1);
ASSERT(!(m_u.table->size & m_u.table->sizeMask));
ASSERT(m_u.table->keyCount <= m_u.table->size / 2);
ASSERT(m_u.table->deletedSentinelCount <= m_u.table->size / 4);
ASSERT(m_u.table->keyCount + m_u.table->deletedSentinelCount <= m_u.table->size / 2);
unsigned indexCount = 0;
unsigned deletedIndexCount = 0;
for (unsigned a = 0; a != m_u.table->size; ++a) {
unsigned entryIndex = m_u.table->entryIndicies[a];
if (entryIndex == emptyEntryIndex)
continue;
if (entryIndex == deletedSentinelIndex) {
++deletedIndexCount;
continue;
}
ASSERT(entryIndex > deletedSentinelIndex);
ASSERT(entryIndex - 1 <= m_u.table->keyCount + m_u.table->deletedSentinelCount);
++indexCount;
for (unsigned b = a + 1; b != m_u.table->size; ++b)
ASSERT(m_u.table->entryIndicies[b] != entryIndex);
}
ASSERT(indexCount == m_u.table->keyCount);
ASSERT(deletedIndexCount == m_u.table->deletedSentinelCount);
ASSERT(m_u.table->entries()[0].key == 0);
unsigned nonEmptyEntryCount = 0;
for (unsigned c = 1; c <= m_u.table->keyCount + m_u.table->deletedSentinelCount; ++c) {
UString::Rep* rep = m_u.table->entries()[c].key;
if (!rep) {
ASSERT(m_u.table->entries()[c].value->isUndefined());
continue;
}
++nonEmptyEntryCount;
unsigned i = rep->computedHash();
unsigned k = 0;
unsigned entryIndex;
while (1) {
entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
ASSERT(entryIndex != emptyEntryIndex);
if (rep == m_u.table->entries()[entryIndex - 1].key)
break;
if (k == 0)
k = 1 | doubleHash(rep->computedHash());
i += k;
}
ASSERT(entryIndex == c + 1);
}
ASSERT(nonEmptyEntryCount == m_u.table->keyCount);
}
#endif // DO_PROPERTYMAP_CONSTENCY_CHECK
} // namespace KJS