Reintroduced dictionary that can use objects as keys.

R=vitalyr@chromium.org
TEST=cctest/test-dictionary

Review URL: http://codereview.chromium.org/7385006

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8761 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 3ce397f9
......@@ -3192,39 +3192,7 @@ int v8::Object::GetIdentityHash() {
ENTER_V8(isolate);
i::HandleScope scope(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> hidden_props_obj(i::GetHiddenProperties(self, true));
if (!hidden_props_obj->IsJSObject()) {
// We failed to create hidden properties. That's a detached
// global proxy.
ASSERT(hidden_props_obj->IsUndefined());
return 0;
}
i::Handle<i::JSObject> hidden_props =
i::Handle<i::JSObject>::cast(hidden_props_obj);
i::Handle<i::String> hash_symbol = isolate->factory()->identity_hash_symbol();
if (hidden_props->HasLocalProperty(*hash_symbol)) {
i::Handle<i::Object> hash = i::GetProperty(hidden_props, hash_symbol);
CHECK(!hash.is_null());
CHECK(hash->IsSmi());
return i::Smi::cast(*hash)->value();
}
int hash_value;
int attempts = 0;
do {
// Generate a random 32-bit hash value but limit range to fit
// within a smi.
hash_value = i::V8::Random(self->GetIsolate()) & i::Smi::kMaxValue;
attempts++;
} while (hash_value == 0 && attempts < 30);
hash_value = hash_value != 0 ? hash_value : 1; // never return 0
CHECK(!i::SetLocalPropertyIgnoreAttributes(
hidden_props,
hash_symbol,
i::Handle<i::Object>(i::Smi::FromInt(hash_value)),
static_cast<PropertyAttributes>(None)).is_null());
return hash_value;
return i::GetIdentityHash(self);
}
......@@ -3235,7 +3203,9 @@ bool v8::Object::SetHiddenValue(v8::Handle<v8::String> key,
ENTER_V8(isolate);
i::HandleScope scope(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, true));
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(
self,
i::JSObject::ALLOW_CREATION));
i::Handle<i::Object> key_obj = Utils::OpenHandle(*key);
i::Handle<i::Object> value_obj = Utils::OpenHandle(*value);
EXCEPTION_PREAMBLE(isolate);
......@@ -3257,7 +3227,9 @@ v8::Local<v8::Value> v8::Object::GetHiddenValue(v8::Handle<v8::String> key) {
return Local<v8::Value>());
ENTER_V8(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, false));
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(
self,
i::JSObject::OMIT_CREATION));
if (hidden_props->IsUndefined()) {
return v8::Local<v8::Value>();
}
......@@ -3279,7 +3251,9 @@ bool v8::Object::DeleteHiddenValue(v8::Handle<v8::String> key) {
ENTER_V8(isolate);
i::HandleScope scope(isolate);
i::Handle<i::JSObject> self = Utils::OpenHandle(this);
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(self, false));
i::Handle<i::Object> hidden_props(i::GetHiddenProperties(
self,
i::JSObject::OMIT_CREATION));
if (hidden_props->IsUndefined()) {
return true;
}
......
......@@ -84,6 +84,14 @@ Handle<NumberDictionary> Factory::NewNumberDictionary(int at_least_space_for) {
}
Handle<ObjectHashTable> Factory::NewObjectHashTable(int at_least_space_for) {
ASSERT(0 <= at_least_space_for);
CALL_HEAP_FUNCTION(isolate(),
ObjectHashTable::Allocate(at_least_space_for),
ObjectHashTable);
}
Handle<DescriptorArray> Factory::NewDescriptorArray(int number_of_descriptors) {
ASSERT(0 <= number_of_descriptors);
CALL_HEAP_FUNCTION(isolate(),
......
......@@ -58,6 +58,8 @@ class Factory {
Handle<StringDictionary> NewStringDictionary(int at_least_space_for);
Handle<ObjectHashTable> NewObjectHashTable(int at_least_space_for);
Handle<DescriptorArray> NewDescriptorArray(int number_of_descriptors);
Handle<DeoptimizationInputData> NewDeoptimizationInputData(
int deopt_entry_count,
......
......@@ -422,43 +422,18 @@ Handle<Object> PreventExtensions(Handle<JSObject> object) {
Handle<Object> GetHiddenProperties(Handle<JSObject> obj,
bool create_if_needed) {
Isolate* isolate = obj->GetIsolate();
Object* holder = obj->BypassGlobalProxy();
if (holder->IsUndefined()) return isolate->factory()->undefined_value();
obj = Handle<JSObject>(JSObject::cast(holder), isolate);
if (obj->HasFastProperties()) {
// If the object has fast properties, check whether the first slot
// in the descriptor array matches the hidden symbol. Since the
// hidden symbols hash code is zero (and no other string has hash
// code zero) it will always occupy the first entry if present.
DescriptorArray* descriptors = obj->map()->instance_descriptors();
if ((descriptors->number_of_descriptors() > 0) &&
(descriptors->GetKey(0) == isolate->heap()->hidden_symbol()) &&
descriptors->IsProperty(0)) {
ASSERT(descriptors->GetType(0) == FIELD);
return Handle<Object>(obj->FastPropertyAt(descriptors->GetFieldIndex(0)),
isolate);
}
}
JSObject::HiddenPropertiesFlag flag) {
CALL_HEAP_FUNCTION(obj->GetIsolate(),
obj->GetHiddenProperties(flag),
Object);
}
// Only attempt to find the hidden properties in the local object and not
// in the prototype chain. Note that HasLocalProperty() can cause a GC in
// the general case in the presence of interceptors.
if (!obj->HasHiddenPropertiesObject()) {
// Hidden properties object not found. Allocate a new hidden properties
// object if requested. Otherwise return the undefined value.
if (create_if_needed) {
Handle<Object> hidden_obj =
isolate->factory()->NewJSObject(isolate->object_function());
CALL_HEAP_FUNCTION(isolate,
obj->SetHiddenPropertiesObject(*hidden_obj), Object);
} else {
return isolate->factory()->undefined_value();
}
}
return Handle<Object>(obj->GetHiddenPropertiesObject(), isolate);
int GetIdentityHash(Handle<JSObject> obj) {
CALL_AND_RETRY(obj->GetIsolate(),
obj->GetIdentityHash(JSObject::ALLOW_CREATION),
return Smi::cast(__object__)->value(),
return 0);
}
......@@ -908,6 +883,15 @@ Handle<FixedArray> GetEnumPropertyKeys(Handle<JSObject> object,
}
Handle<ObjectHashTable> PutIntoObjectHashTable(Handle<ObjectHashTable> table,
Handle<JSObject> key,
Handle<Object> value) {
CALL_HEAP_FUNCTION(table->GetIsolate(),
table->Put(*key, *value),
ObjectHashTable);
}
bool EnsureCompiled(Handle<SharedFunctionInfo> shared,
ClearExceptionFlag flag) {
return shared->is_compiled() || CompileLazyShared(shared, flag);
......
......@@ -264,9 +264,13 @@ Handle<Object> GetPrototype(Handle<Object> obj);
Handle<Object> SetPrototype(Handle<JSObject> obj, Handle<Object> value);
// Return the object's hidden properties object. If the object has no hidden
// properties and create_if_needed is true, then a new hidden property object
// will be allocated. Otherwise the Heap::undefined_value is returned.
Handle<Object> GetHiddenProperties(Handle<JSObject> obj, bool create_if_needed);
// properties and HiddenPropertiesFlag::ALLOW_CREATION is passed, then a new
// hidden property object will be allocated. Otherwise Heap::undefined_value
// is returned.
Handle<Object> GetHiddenProperties(Handle<JSObject> obj,
JSObject::HiddenPropertiesFlag flag);
int GetIdentityHash(Handle<JSObject> obj);
Handle<Object> DeleteElement(Handle<JSObject> obj, uint32_t index);
Handle<Object> DeleteProperty(Handle<JSObject> obj, Handle<String> prop);
......@@ -343,6 +347,10 @@ Handle<Object> SetPrototype(Handle<JSFunction> function,
Handle<Object> PreventExtensions(Handle<JSObject> object);
Handle<ObjectHashTable> PutIntoObjectHashTable(Handle<ObjectHashTable> table,
Handle<JSObject> key,
Handle<Object> value);
// Does lazy compilation of the given function. Returns true on success and
// false if the compilation resulted in a stack overflow.
enum ClearExceptionFlag { KEEP_EXCEPTION, CLEAR_EXCEPTION };
......
......@@ -4252,6 +4252,11 @@ MaybeObject* JSObject::SetHiddenPropertiesObject(Object* hidden_obj) {
}
bool JSObject::HasHiddenProperties() {
return !GetHiddenProperties(OMIT_CREATION)->ToObjectChecked()->IsUndefined();
}
bool JSObject::HasElement(uint32_t index) {
return HasElementWithReceiver(this, index);
}
......@@ -4367,6 +4372,31 @@ MaybeObject* StringDictionaryShape::AsObject(String* key) {
}
bool ObjectHashTableShape::IsMatch(JSObject* key, Object* other) {
return key == JSObject::cast(other);
}
uint32_t ObjectHashTableShape::Hash(JSObject* key) {
MaybeObject* maybe_hash = key->GetIdentityHash(JSObject::OMIT_CREATION);
ASSERT(!maybe_hash->IsFailure());
return Smi::cast(maybe_hash->ToObjectUnchecked())->value();
}
uint32_t ObjectHashTableShape::HashForObject(JSObject* key, Object* other) {
MaybeObject* maybe_hash = JSObject::cast(other)->GetIdentityHash(
JSObject::OMIT_CREATION);
ASSERT(!maybe_hash->IsFailure());
return Smi::cast(maybe_hash->ToObjectUnchecked())->value();
}
MaybeObject* ObjectHashTableShape::AsObject(JSObject* key) {
return key;
}
void Map::ClearCodeCache(Heap* heap) {
// No write barrier is needed since empty_fixed_array is not in new space.
// Please note this function is used during marking:
......
......@@ -2967,6 +2967,91 @@ MaybeObject* JSObject::NormalizeElements() {
}
MaybeObject* JSObject::GetHiddenProperties(HiddenPropertiesFlag flag) {
Isolate* isolate = GetIsolate();
Heap* heap = isolate->heap();
Object* holder = BypassGlobalProxy();
if (holder->IsUndefined()) return heap->undefined_value();
JSObject* obj = JSObject::cast(holder);
if (obj->HasFastProperties()) {
// If the object has fast properties, check whether the first slot
// in the descriptor array matches the hidden symbol. Since the
// hidden symbols hash code is zero (and no other string has hash
// code zero) it will always occupy the first entry if present.
DescriptorArray* descriptors = obj->map()->instance_descriptors();
if ((descriptors->number_of_descriptors() > 0) &&
(descriptors->GetKey(0) == heap->hidden_symbol()) &&
descriptors->IsProperty(0)) {
ASSERT(descriptors->GetType(0) == FIELD);
return obj->FastPropertyAt(descriptors->GetFieldIndex(0));
}
}
// Only attempt to find the hidden properties in the local object and not
// in the prototype chain.
if (!obj->HasHiddenPropertiesObject()) {
// Hidden properties object not found. Allocate a new hidden properties
// object if requested. Otherwise return the undefined value.
if (flag == ALLOW_CREATION) {
Object* hidden_obj;
{ MaybeObject* maybe_obj = heap->AllocateJSObject(
isolate->context()->global_context()->object_function());
if (!maybe_obj->ToObject(&hidden_obj)) return maybe_obj;
}
return obj->SetHiddenPropertiesObject(hidden_obj);
} else {
return heap->undefined_value();
}
}
return obj->GetHiddenPropertiesObject();
}
MaybeObject* JSObject::GetIdentityHash(HiddenPropertiesFlag flag) {
Isolate* isolate = GetIsolate();
Object* hidden_props_obj;
{ MaybeObject* maybe_obj = GetHiddenProperties(flag);
if (!maybe_obj->ToObject(&hidden_props_obj)) return maybe_obj;
}
if (!hidden_props_obj->IsJSObject()) {
// We failed to create hidden properties. That's a detached
// global proxy.
ASSERT(hidden_props_obj->IsUndefined());
return Smi::FromInt(0);
}
JSObject* hidden_props = JSObject::cast(hidden_props_obj);
String* hash_symbol = isolate->heap()->identity_hash_symbol();
{
// Note that HasLocalProperty() can cause a GC in the general case in the
// presence of interceptors.
AssertNoAllocation no_alloc;
if (hidden_props->HasLocalProperty(hash_symbol)) {
MaybeObject* hash = hidden_props->GetProperty(hash_symbol);
return Smi::cast(hash->ToObjectChecked());
}
}
int hash_value;
int attempts = 0;
do {
// Generate a random 32-bit hash value but limit range to fit
// within a smi.
hash_value = V8::Random(isolate) & Smi::kMaxValue;
attempts++;
} while (hash_value == 0 && attempts < 30);
hash_value = hash_value != 0 ? hash_value : 1; // never return 0
Smi* hash = Smi::FromInt(hash_value);
{ MaybeObject* result = hidden_props->SetLocalPropertyIgnoreAttributes(
hash_symbol,
hash,
static_cast<PropertyAttributes>(None));
if (result->IsFailure()) return result;
}
return hash;
}
MaybeObject* JSObject::DeletePropertyPostInterceptor(String* name,
DeleteMode mode) {
// Check local property, ignore interceptor.
......@@ -10380,6 +10465,8 @@ template class HashTable<CompilationCacheShape, HashTableKey*>;
template class HashTable<MapCacheShape, HashTableKey*>;
template class HashTable<ObjectHashTableShape, JSObject*>;
template class Dictionary<StringDictionaryShape, String*>;
template class Dictionary<NumberDictionaryShape, uint32_t>;
......@@ -11691,6 +11778,64 @@ MaybeObject* StringDictionary::TransformPropertiesToFastFor(
}
Object* ObjectHashTable::Lookup(JSObject* key) {
// If the object does not have an identity hash, it was never used as a key.
MaybeObject* maybe_hash = key->GetIdentityHash(JSObject::OMIT_CREATION);
if (maybe_hash->IsFailure()) return GetHeap()->undefined_value();
int entry = FindEntry(key);
if (entry == kNotFound) return GetHeap()->undefined_value();
return get(EntryToIndex(entry) + 1);
}
MaybeObject* ObjectHashTable::Put(JSObject* key, Object* value) {
// Make sure the key object has an identity hash code.
int hash;
{ MaybeObject* maybe_hash = key->GetIdentityHash(JSObject::ALLOW_CREATION);
if (maybe_hash->IsFailure()) return maybe_hash;
hash = Smi::cast(maybe_hash->ToObjectUnchecked())->value();
}
int entry = FindEntry(key);
// Check whether to perform removal operation.
if (value->IsUndefined()) {
if (entry == kNotFound) return this;
RemoveEntry(entry);
return Shrink(key);
}
// Key is already in table, just overwrite value.
if (entry != kNotFound) {
set(EntryToIndex(entry) + 1, value);
return this;
}
// Check whether the hash table should be extended.
Object* obj;
{ MaybeObject* maybe_obj = EnsureCapacity(1, key);
if (!maybe_obj->ToObject(&obj)) return maybe_obj;
}
ObjectHashTable* table = ObjectHashTable::cast(obj);
table->AddEntry(table->FindInsertionEntry(hash), key, value);
return table;
}
void ObjectHashTable::AddEntry(int entry, JSObject* key, Object* value) {
set(EntryToIndex(entry), key);
set(EntryToIndex(entry) + 1, value);
ElementAdded();
}
void ObjectHashTable::RemoveEntry(int entry) {
Object* null_value = GetHeap()->null_value();
set(EntryToIndex(entry), null_value);
set(EntryToIndex(entry) + 1, null_value);
ElementRemoved();
}
#ifdef ENABLE_DEBUGGER_SUPPORT
// Check if there is a break point at this code position.
bool DebugInfo::HasBreakPoint(int code_position) {
......
......@@ -1638,6 +1638,23 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT inline MaybeObject* SetHiddenPropertiesObject(
Object* hidden_obj);
// Indicates whether the hidden properties object should be created.
enum HiddenPropertiesFlag { ALLOW_CREATION, OMIT_CREATION };
// Retrieves the hidden properties object.
//
// The undefined value might be returned in case no hidden properties object
// is present and creation was omitted.
inline bool HasHiddenProperties();
MUST_USE_RESULT MaybeObject* GetHiddenProperties(HiddenPropertiesFlag flag);
// Retrieves a permanent object identity hash code.
//
// The identity hash is stored as a hidden property. The undefined value might
// be returned in case no hidden properties object is present and creation was
// omitted.
MUST_USE_RESULT MaybeObject* GetIdentityHash(HiddenPropertiesFlag flag);
MUST_USE_RESULT MaybeObject* DeleteProperty(String* name, DeleteMode mode);
MUST_USE_RESULT MaybeObject* DeleteElement(uint32_t index, DeleteMode mode);
......@@ -2933,6 +2950,40 @@ class NumberDictionary: public Dictionary<NumberDictionaryShape, uint32_t> {
};
class ObjectHashTableShape {
public:
static inline bool IsMatch(JSObject* key, Object* other);
static inline uint32_t Hash(JSObject* key);
static inline uint32_t HashForObject(JSObject* key, Object* object);
MUST_USE_RESULT static inline MaybeObject* AsObject(JSObject* key);
static const int kPrefixSize = 0;
static const int kEntrySize = 2;
};
// ObjectHashTable maps keys that are JavaScript objects to object values by
// using the identity hash of the key for hashing purposes.
class ObjectHashTable: public HashTable<ObjectHashTableShape, JSObject*> {
public:
static inline ObjectHashTable* cast(Object* obj) {
ASSERT(obj->IsHashTable());
return reinterpret_cast<ObjectHashTable*>(obj);
}
// Looks up the value associated with the given key. The undefined value is
// returned in case the key is not present.
Object* Lookup(JSObject* key);
// Adds (or overwrites) the value associated with the given key. Mapping a
// key to the undefined value causes removal of the whole entry.
MUST_USE_RESULT MaybeObject* Put(JSObject* key, Object* value);
private:
void AddEntry(int entry, JSObject* key, Object* value);
void RemoveEntry(int entry);
};
// JSFunctionResultCache caches results of some JSFunction invocation.
// It is a fixed array with fixed structure:
// [0]: factory function
......
......@@ -4532,7 +4532,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetLocalPropertyNames) {
for (int i = 0; i < length; i++) {
jsproto->GetLocalPropertyNames(*names,
i == 0 ? 0 : local_property_count[i - 1]);
if (!GetHiddenProperties(jsproto, false)->IsUndefined()) {
if (jsproto->HasHiddenProperties()) {
proto_with_hidden_properties++;
}
if (i < length - 1) {
......
......@@ -65,6 +65,7 @@ SOURCES = {
'test-debug.cc',
'test-decls.cc',
'test-deoptimization.cc',
'test-dictionary.cc',
'test-diy-fp.cc',
'test-double.cc',
'test-dtoa.cc',
......
......@@ -61,6 +61,7 @@
'test-debug.cc',
'test-decls.cc',
'test-deoptimization.cc',
'test-dictionary.cc',
'test-diy-fp.cc',
'test-double.cc',
'test-dtoa.cc',
......
// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "v8.h"
#include "api.h"
#include "debug.h"
#include "execution.h"
#include "factory.h"
#include "macro-assembler.h"
#include "objects.h"
#include "global-handles.h"
#include "cctest.h"
using namespace v8::internal;
TEST(ObjectHashTable) {
v8::HandleScope scope;
LocalContext context;
Handle<ObjectHashTable> table = FACTORY->NewObjectHashTable(23);
Handle<JSObject> a = FACTORY->NewJSArray(7);
Handle<JSObject> b = FACTORY->NewJSArray(11);
table = PutIntoObjectHashTable(table, a, b);
CHECK_EQ(table->NumberOfElements(), 1);
CHECK_EQ(table->Lookup(*a), *b);
CHECK_EQ(table->Lookup(*b), HEAP->undefined_value());
// Keys still have to be valid after objects were moved.
HEAP->CollectGarbage(NEW_SPACE);
CHECK_EQ(table->NumberOfElements(), 1);
CHECK_EQ(table->Lookup(*a), *b);
CHECK_EQ(table->Lookup(*b), HEAP->undefined_value());
// Keys that are overwritten should not change number of elements.
table = PutIntoObjectHashTable(table, a, FACTORY->NewJSArray(13));
CHECK_EQ(table->NumberOfElements(), 1);
CHECK_NE(table->Lookup(*a), *b);
// Keys mapped to undefined should be removed permanently.
table = PutIntoObjectHashTable(table, a, FACTORY->undefined_value());
CHECK_EQ(table->NumberOfElements(), 0);
CHECK_EQ(table->NumberOfDeletedElements(), 1);
CHECK_EQ(table->Lookup(*a), HEAP->undefined_value());
// Keys should map back to their respective values.
for (int i = 0; i < 100; i++) {
Handle<JSObject> key = FACTORY->NewJSArray(7);
Handle<JSObject> value = FACTORY->NewJSArray(11);
table = PutIntoObjectHashTable(table, key, value);
CHECK_EQ(table->NumberOfElements(), i + 1);
CHECK_NE(table->FindEntry(*key), ObjectHashTable::kNotFound);
CHECK_EQ(table->Lookup(*key), *value);
}
// Keys never added to the map should not be found.
for (int i = 0; i < 1000; i++) {
Handle<JSObject> o = FACTORY->NewJSArray(100);
CHECK_EQ(table->FindEntry(*o), ObjectHashTable::kNotFound);
CHECK_EQ(table->Lookup(*o), HEAP->undefined_value());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment