From 1c747846df3be9021aff2b0fc402cdd3a239e86b Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Wed, 25 May 2011 10:36:13 +1000 Subject: [PATCH 06/11] Add custom object compare callback A global custom object comparison callback can be set with: V8::SetUserObjectComparisonCallbackFunction() When two JSObjects are compared (== or !=), if either one has the MarkAsUseUserObjectComparison() bit set, the custom comparison callback is invoked to do the actual comparison. This is useful when you have "value" objects that you want to compare as equal, even though they are actually different JS object instances. --- include/v8.h | 13 +++++++++++++ src/api.cc | 22 ++++++++++++++++++++++ src/arm/code-stubs-arm.cc | 43 +++++++++++++++++++++++++++++++++++++++++-- src/factory.cc | 8 ++++++++ src/ia32/code-stubs-ia32.cc | 39 +++++++++++++++++++++++++++++++++++++++ src/isolate.cc | 7 +++++++ src/isolate.h | 8 ++++++++ src/objects-inl.h | 21 ++++++++++++++++++--- src/objects.cc | 8 ++++---- src/objects.h | 12 ++++++++++-- src/runtime.cc | 23 +++++++++++++++++++++++ src/runtime.h | 1 + src/x64/code-stubs-x64.cc | 37 +++++++++++++++++++++++++++++++++++++ 13 files changed, 231 insertions(+), 11 deletions(-) diff --git a/include/v8.h b/include/v8.h index c094d08..6baf2b2 100644 --- a/include/v8.h +++ b/include/v8.h @@ -2501,6 +2501,12 @@ class V8EXPORT ObjectTemplate : public Template { bool HasExternalResource(); void SetHasExternalResource(bool value); + /** + * Mark object instances of the template as using the user object + * comparison callback. + */ + void MarkAsUseUserObjectComparison(); + private: ObjectTemplate(); static Local New(Handle constructor); @@ -2720,6 +2726,10 @@ typedef void (*FailedAccessCheckCallback)(Local target, AccessType type, Local data); +// --- User Object Comparisoa nCallback --- +typedef bool (*UserObjectComparisonCallback)(Local lhs, + Local rhs); + // --- AllowCodeGenerationFromStrings callbacks --- /** @@ -3046,6 +3056,9 @@ class V8EXPORT V8 { /** Callback function for reporting failed access checks.*/ static void SetFailedAccessCheckCallbackFunction(FailedAccessCheckCallback); + /** Callback for user object comparisons */ + static void SetUserObjectComparisonCallbackFunction(UserObjectComparisonCallback); + /** * Enables the host application to receive a notification before a * garbage collection. Allocations are not allowed in the diff --git a/src/api.cc b/src/api.cc index 526aa02..6f6297d 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1464,6 +1464,17 @@ void ObjectTemplate::SetHasExternalResource(bool value) } +void ObjectTemplate::MarkAsUseUserObjectComparison() +{ + i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate(); + if (IsDeadCheck(isolate, "v8::ObjectTemplate::MarkAsUseUserObjectComparison()")) { + return; + } + ENTER_V8(isolate); + EnsureConstructor(this); + Utils::OpenHandle(this)->set_use_user_object_comparison(i::Smi::FromInt(1)); +} + // --- S c r i p t D a t a --- @@ -5106,6 +5117,17 @@ void V8::SetFailedAccessCheckCallbackFunction( isolate->SetFailedAccessCheckCallback(callback); } + +void V8::SetUserObjectComparisonCallbackFunction( + UserObjectComparisonCallback callback) { + i::Isolate* isolate = i::Isolate::Current(); + if (IsDeadCheck(isolate, "v8::V8::SetUserObjectComparisonCallbackFunction()")) { + return; + } + isolate->SetUserObjectComparisonCallback(callback); +} + + void V8::AddObjectGroup(Persistent* objects, size_t length, RetainedObjectInfo* info) { diff --git a/src/arm/code-stubs-arm.cc b/src/arm/code-stubs-arm.cc index 00ac676..a67b3a0 100644 --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -1494,6 +1494,37 @@ void CompareStub::Generate(MacroAssembler* masm) { // NOTICE! This code is only reached after a smi-fast-case check, so // it is certain that at least one operand isn't a smi. + { + Label not_user_equal, user_equal; + __ and_(r2, r1, Operand(r0)); + __ tst(r2, Operand(kSmiTagMask)); + __ b(eq, ¬_user_equal); + + __ CompareObjectType(r0, r2, r4, JS_OBJECT_TYPE); + __ b(ne, ¬_user_equal); + + __ CompareObjectType(r1, r3, r4, JS_OBJECT_TYPE); + __ b(ne, ¬_user_equal); + + __ ldrb(r2, FieldMemOperand(r2, Map::kBitField2Offset)); + __ and_(r2, r2, Operand(1 << Map::kUseUserObjectComparison)); + __ cmp(r2, Operand(1 << Map::kUseUserObjectComparison)); + __ b(eq, &user_equal); + + __ ldrb(r3, FieldMemOperand(r3, Map::kBitField2Offset)); + __ and_(r3, r3, Operand(1 << Map::kUseUserObjectComparison)); + __ cmp(r3, Operand(1 << Map::kUseUserObjectComparison)); + __ b(ne, ¬_user_equal); + + __ bind(&user_equal); + + __ Push(r0, r1); + __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1); + + __ bind(¬_user_equal); + } + + // Handle the case where the objects are identical. Either returns the answer // or goes to slow. Only falls through if the objects were not identical. EmitIdenticalObjectComparison(masm, &slow, cc_, never_nan_nan_); @@ -6544,10 +6575,18 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ and_(r2, r1, Operand(r0)); __ JumpIfSmi(r2, &miss); - __ CompareObjectType(r0, r2, r2, JS_OBJECT_TYPE); + __ CompareObjectType(r0, r2, r3, JS_OBJECT_TYPE); __ b(ne, &miss); - __ CompareObjectType(r1, r2, r2, JS_OBJECT_TYPE); + __ ldrb(r2, FieldMemOperand(r2, Map::kBitField2Offset)); + __ and_(r2, r2, Operand(1 << Map::kUseUserObjectComparison)); + __ cmp(r2, Operand(1 << Map::kUseUserObjectComparison)); + __ b(eq, &miss); + __ CompareObjectType(r1, r2, r3, JS_OBJECT_TYPE); __ b(ne, &miss); + __ ldrb(r2, FieldMemOperand(r2, Map::kBitField2Offset)); + __ and_(r2, r2, Operand(1 << Map::kUseUserObjectComparison)); + __ cmp(r2, Operand(1 << Map::kUseUserObjectComparison)); + __ b(eq, &miss); ASSERT(GetCondition() == eq); __ sub(r0, r0, Operand(r1)); diff --git a/src/factory.cc b/src/factory.cc index e3cccae..96d3458 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -1120,6 +1120,7 @@ Handle Factory::CreateApiFunction( int internal_field_count = 0; bool has_external_resource = false; + bool use_user_object_comparison = false; if (!obj->instance_template()->IsUndefined()) { Handle instance_template = @@ -1129,6 +1130,8 @@ Handle Factory::CreateApiFunction( Smi::cast(instance_template->internal_field_count())->value(); has_external_resource = !instance_template->has_external_resource()->IsUndefined(); + use_user_object_comparison = + !instance_template->use_user_object_comparison()->IsUndefined(); } int instance_size = kPointerSize * internal_field_count; @@ -1173,6 +1176,11 @@ Handle Factory::CreateApiFunction( map->set_has_external_resource(true); } + // Mark as using user object comparison if needed + if (use_user_object_comparison) { + map->set_use_user_object_comparison(true); + } + // Mark as undetectable if needed. if (obj->undetectable()) { map->set_is_undetectable(); diff --git a/src/ia32/code-stubs-ia32.cc b/src/ia32/code-stubs-ia32.cc index 6cc80d3..cabf78f 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -3931,6 +3931,39 @@ void CompareStub::Generate(MacroAssembler* masm) { // NOTICE! This code is only reached after a smi-fast-case check, so // it is certain that at least one operand isn't a smi. + { + Label not_user_equal, user_equal; + __ test(eax, Immediate(kSmiTagMask)); + __ j(zero, ¬_user_equal); + __ test(edx, Immediate(kSmiTagMask)); + __ j(zero, ¬_user_equal); + + __ CmpObjectType(eax, JS_OBJECT_TYPE, ebx); + __ j(not_equal, ¬_user_equal); + + __ CmpObjectType(edx, JS_OBJECT_TYPE, ecx); + __ j(not_equal, ¬_user_equal); + + __ test_b(FieldOperand(ebx, Map::kBitField2Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &user_equal); + __ test_b(FieldOperand(ecx, Map::kBitField2Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &user_equal); + + __ jmp(¬_user_equal); + + __ bind(&user_equal); + + __ pop(ebx); // Return address. + __ push(eax); + __ push(edx); + __ push(ebx); + __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1); + + __ bind(¬_user_equal); + } + // Identical objects can be compared fast, but there are some tricky cases // for NaN and undefined. { @@ -6409,8 +6442,14 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ CmpObjectType(eax, JS_OBJECT_TYPE, ecx); __ j(not_equal, &miss, Label::kNear); + __ test_b(FieldOperand(ecx, Map::kBitField2Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &miss, Label::kNear); __ CmpObjectType(edx, JS_OBJECT_TYPE, ecx); __ j(not_equal, &miss, Label::kNear); + __ test_b(FieldOperand(ecx, Map::kBitField2Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &miss, Label::kNear); ASSERT(GetCondition() == equal); __ sub(eax, edx); diff --git a/src/isolate.cc b/src/isolate.cc index 951f428..6a86bf1 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -96,6 +96,7 @@ void ThreadLocalTop::InitializeInternal() { thread_id_ = ThreadId::Invalid(); external_caught_exception_ = false; failed_access_check_callback_ = NULL; + user_object_comparison_callback_ = NULL; save_context_ = NULL; catcher_ = NULL; } @@ -717,6 +718,12 @@ void Isolate::SetFailedAccessCheckCallback( thread_local_top()->failed_access_check_callback_ = callback; } + +void Isolate::SetUserObjectComparisonCallback( + v8::UserObjectComparisonCallback callback) { + thread_local_top()->user_object_comparison_callback_ = callback; +} + void Isolate::ReportFailedAccessCheck(JSObject* receiver, v8::AccessType type) { if (!thread_local_top()->failed_access_check_callback_) return; diff --git a/src/isolate.h b/src/isolate.h index 01ab04e..06ef22d 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -255,6 +255,9 @@ class ThreadLocalTop BASE_EMBEDDED { // Call back function to report unsafe JS accesses. v8::FailedAccessCheckCallback failed_access_check_callback_; + // Call back function for user object comparisons + v8::UserObjectComparisonCallback user_object_comparison_callback_; + // Whether out of memory exceptions should be ignored. bool ignore_out_of_memory_; @@ -701,6 +704,11 @@ class Isolate { void SetFailedAccessCheckCallback(v8::FailedAccessCheckCallback callback); void ReportFailedAccessCheck(JSObject* receiver, v8::AccessType type); + void SetUserObjectComparisonCallback(v8::UserObjectComparisonCallback callback); + inline v8::UserObjectComparisonCallback UserObjectComparisonCallback() { + return thread_local_top()->user_object_comparison_callback_; + } + // Exception throwing support. The caller should use the result // of Throw() as its return value. Failure* Throw(Object* exception, MessageLocation* location = NULL); diff --git a/src/objects-inl.h b/src/objects-inl.h index 375df0f..4657482 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2859,14 +2859,14 @@ bool Map::is_extensible() { void Map::set_attached_to_shared_function_info(bool value) { if (value) { - set_bit_field2(bit_field2() | (1 << kAttachedToSharedFunctionInfo)); + set_bit_field3(bit_field3() | (1 << kAttachedToSharedFunctionInfo)); } else { - set_bit_field2(bit_field2() & ~(1 << kAttachedToSharedFunctionInfo)); + set_bit_field3(bit_field3() & ~(1 << kAttachedToSharedFunctionInfo)); } } bool Map::attached_to_shared_function_info() { - return ((1 << kAttachedToSharedFunctionInfo) & bit_field2()) != 0; + return ((1 << kAttachedToSharedFunctionInfo) & bit_field3()) != 0; } @@ -2896,6 +2896,19 @@ bool Map::has_external_resource() } +void Map::set_use_user_object_comparison(bool value) { + if (value) { + set_bit_field2(bit_field2() | (1 << kUseUserObjectComparison)); + } else { + set_bit_field2(bit_field2() & ~(1 << kUseUserObjectComparison)); + } +} + +bool Map::use_user_object_comparison() { + return ((1 << kUseUserObjectComparison) & bit_field2()) != 0; +} + + void Map::set_named_interceptor_is_fallback(bool value) { if (value) { @@ -3429,6 +3442,8 @@ ACCESSORS(ObjectTemplateInfo, internal_field_count, Object, kInternalFieldCountOffset) ACCESSORS(ObjectTemplateInfo, has_external_resource, Object, kHasExternalResourceOffset) +ACCESSORS(ObjectTemplateInfo, use_user_object_comparison, Object, + kUseUserObjectComparisonOffset) ACCESSORS(SignatureInfo, receiver, Object, kReceiverOffset) ACCESSORS(SignatureInfo, args, Object, kArgsOffset) diff --git a/src/objects.cc b/src/objects.cc index 8e1773f..28e6d79 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -7159,8 +7159,8 @@ void SharedFunctionInfo::DetachInitialMap() { Map* map = reinterpret_cast(initial_map()); // Make the map remember to restore the link if it survives the GC. - map->set_bit_field2( - map->bit_field2() | (1 << Map::kAttachedToSharedFunctionInfo)); + map->set_bit_field3( + map->bit_field3() | (1 << Map::kAttachedToSharedFunctionInfo)); // Undo state changes made by StartInobjectTracking (except the // construction_count). This way if the initial map does not survive the GC @@ -7180,8 +7180,8 @@ void SharedFunctionInfo::DetachInitialMap() { // Called from GC, hence reinterpret_cast and unchecked accessors. void SharedFunctionInfo::AttachInitialMap(Map* map) { - map->set_bit_field2( - map->bit_field2() & ~(1 << Map::kAttachedToSharedFunctionInfo)); + map->set_bit_field3( + map->bit_field3() & ~(1 << Map::kAttachedToSharedFunctionInfo)); // Resume inobject slack tracking. set_initial_map(map); diff --git a/src/objects.h b/src/objects.h index 71895be..881c24e 100644 --- a/src/objects.h +++ b/src/objects.h @@ -4084,6 +4084,11 @@ class Map: public HeapObject { inline void set_has_external_resource(bool value); inline bool has_external_resource(); + // Tells whether the user object comparison callback should be used for + // comparisons involving this object + inline void set_use_user_object_comparison(bool value); + inline bool use_user_object_comparison(); + // [prototype]: implicit prototype object. DECL_ACCESSORS(prototype, Object) @@ -4303,7 +4308,7 @@ class Map: public HeapObject { static const int kIsExtensible = 0; static const int kFunctionWithPrototype = 1; static const int kStringWrapperSafeForDefaultValueOf = 2; - static const int kAttachedToSharedFunctionInfo = 3; + static const int kUseUserObjectComparison = 3; // No bits can be used after kElementsKindFirstBit, they are all reserved for // storing ElementKind. static const int kElementsKindShift = 4; @@ -4322,6 +4327,7 @@ class Map: public HeapObject { static const int kIsShared = 0; static const int kNamedInterceptorIsFallback = 1; static const int kHasInstanceCallHandler = 2; + static const int kAttachedToSharedFunctionInfo = 3; // Layout of the default cache. It holds alternating name and code objects. static const int kCodeCacheEntrySize = 2; @@ -7306,6 +7312,7 @@ class ObjectTemplateInfo: public TemplateInfo { DECL_ACCESSORS(constructor, Object) DECL_ACCESSORS(internal_field_count, Object) DECL_ACCESSORS(has_external_resource, Object) + DECL_ACCESSORS(use_user_object_comparison, Object) static inline ObjectTemplateInfo* cast(Object* obj); @@ -7323,7 +7330,8 @@ class ObjectTemplateInfo: public TemplateInfo { static const int kInternalFieldCountOffset = kConstructorOffset + kPointerSize; static const int kHasExternalResourceOffset = kInternalFieldCountOffset + kPointerSize; - static const int kSize = kHasExternalResourceOffset + kPointerSize; + static const int kUseUserObjectComparisonOffset = kHasExternalResourceOffset + kPointerSize; + static const int kSize = kUseUserObjectComparisonOffset + kPointerSize; }; diff --git a/src/runtime.cc b/src/runtime.cc index 0388a77..24b3de0 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -7008,6 +7008,29 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringEquals) { } +RUNTIME_FUNCTION(MaybeObject*, Runtime_UserObjectEquals) { + NoHandleAllocation ha; + ASSERT(args.length() == 2); + + CONVERT_CHECKED(JSObject, lhs, args[1]); + CONVERT_CHECKED(JSObject, rhs, args[0]); + + bool result; + + v8::UserObjectComparisonCallback callback = isolate->UserObjectComparisonCallback(); + if (callback) { + HandleScope scope(isolate); + Handle lhs_handle(lhs); + Handle rhs_handle(rhs); + result = callback(v8::Utils::ToLocal(lhs_handle), v8::Utils::ToLocal(rhs_handle)); + } else { + result = (lhs == rhs); + } + + return Smi::FromInt(result?0:1); +} + + RUNTIME_FUNCTION(MaybeObject*, Runtime_NumberCompare) { NoHandleAllocation ha; ASSERT(args.length() == 3); diff --git a/src/runtime.h b/src/runtime.h index 284f723..5d6bf5e 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -157,6 +157,7 @@ namespace internal { /* Comparisons */ \ F(NumberEquals, 2, 1) \ F(StringEquals, 2, 1) \ + F(UserObjectEquals, 2, 1) \ \ F(NumberCompare, 3, 1) \ F(SmiLexicographicCompare, 2, 1) \ diff --git a/src/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc index 6ab12fc..3be2c4b 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -3005,6 +3005,37 @@ void CompareStub::Generate(MacroAssembler* masm) { // NOTICE! This code is only reached after a smi-fast-case check, so // it is certain that at least one operand isn't a smi. + { + Label not_user_equal, user_equal; + __ JumpIfSmi(rax, ¬_user_equal); + __ JumpIfSmi(rdx, ¬_user_equal); + + __ CmpObjectType(rax, JS_OBJECT_TYPE, rbx); + __ j(not_equal, ¬_user_equal); + + __ CmpObjectType(rdx, JS_OBJECT_TYPE, rcx); + __ j(not_equal, ¬_user_equal); + + __ testb(FieldOperand(rbx, Map::kBitField2Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &user_equal); + __ testb(FieldOperand(rcx, Map::kBitField2Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &user_equal); + + __ jmp(¬_user_equal); + + __ bind(&user_equal); + + __ pop(rbx); // Return address. + __ push(rax); + __ push(rdx); + __ push(rbx); + __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1); + + __ bind(¬_user_equal); + } + // Two identical objects are equal unless they are both NaN or undefined. { Label not_identical; @@ -5337,8 +5368,14 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ CmpObjectType(rax, JS_OBJECT_TYPE, rcx); __ j(not_equal, &miss, Label::kNear); + __ testb(FieldOperand(rcx, Map::kBitField2Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &miss, Label::kNear); __ CmpObjectType(rdx, JS_OBJECT_TYPE, rcx); __ j(not_equal, &miss, Label::kNear); + __ testb(FieldOperand(rcx, Map::kBitField2Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &miss, Label::kNear); ASSERT(GetCondition() == equal); __ subq(rax, rdx); -- 1.7.4.4