From bec11b8b7f89d135e7d9a823ac4fe98c70d017cf Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Mon, 27 Jun 2011 14:57:28 +1000 Subject: [PATCH 08/16] 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 | 19 +++++++++++++++++++ src/arm/code-stubs-arm.cc | 42 ++++++++++++++++++++++++++++++++++++++++-- src/factory.cc | 8 ++++++++ src/ia32/code-stubs-ia32.cc | 40 ++++++++++++++++++++++++++++++++++++++++ src/isolate.h | 8 ++++++++ src/objects-inl.h | 15 +++++++++++++++ src/objects.h | 10 +++++++++- src/runtime.cc | 23 +++++++++++++++++++++++ src/runtime.h | 1 + src/top.cc | 5 +++++ src/x64/code-stubs-x64.cc | 37 +++++++++++++++++++++++++++++++++++++ 12 files changed, 218 insertions(+), 3 deletions(-) diff --git a/include/v8.h b/include/v8.h index 8891dab..d5d6972 100644 --- a/include/v8.h +++ b/include/v8.h @@ -2365,6 +2365,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); @@ -2565,6 +2571,10 @@ typedef void (*FailedAccessCheckCallback)(Local target, AccessType type, Local data); +// --- U s e r O b j e c t C o m p a r i s o n C a l l b a c k --- +typedef bool (*UserObjectComparisonCallback)(Local lhs, + Local rhs); + // --- G a r b a g e C o l l e c t i o n C a l l b a c k s /** @@ -2815,6 +2825,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 ff74efb..2436031 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1321,6 +1321,16 @@ 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 --- @@ -4632,6 +4642,15 @@ 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 a2626bf..749c9be 100644 --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -1563,6 +1563,36 @@ 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::kBitField3Offset)); + __ and_(r2, r2, Operand(1 << Map::kUseUserObjectComparison)); + __ cmp(r2, Operand(1 << Map::kUseUserObjectComparison)); + __ b(eq, &user_equal); + + __ ldrb(r3, FieldMemOperand(r3, Map::kBitField3Offset)); + __ 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_); @@ -5802,10 +5832,18 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ tst(r2, Operand(kSmiTagMask)); __ b(eq, &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::kBitField3Offset)); + __ 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::kBitField3Offset)); + __ 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 d530a75..6f8c7de 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -998,6 +998,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 = @@ -1007,6 +1008,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; @@ -1051,6 +1054,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 afa599e..0964ab9 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -3447,6 +3447,40 @@ void CompareStub::Generate(MacroAssembler* masm) { __ Assert(not_zero, "Unexpected smi operands."); } + { + NearLabel 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::kBitField3Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &user_equal); + __ test_b(FieldOperand(ecx, Map::kBitField3Offset), + 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); + } + + // 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. @@ -5592,8 +5626,14 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ CmpObjectType(eax, JS_OBJECT_TYPE, ecx); __ j(not_equal, &miss, not_taken); + __ test_b(FieldOperand(ecx, Map::kBitField3Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &miss); __ CmpObjectType(edx, JS_OBJECT_TYPE, ecx); __ j(not_equal, &miss, not_taken); + __ test_b(FieldOperand(ecx, Map::kBitField3Offset), + 1 << Map::kUseUserObjectComparison); + __ j(not_zero, &miss); ASSERT(GetCondition() == equal); __ sub(eax, Operand(edx)); diff --git a/src/isolate.h b/src/isolate.h index 35ffcb4..8130397 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -267,6 +267,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_; + private: void InitializeInternal(); @@ -699,6 +702,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 1c7f83e..1765441 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2552,6 +2552,19 @@ bool Map::has_external_resource() } +void Map::set_use_user_object_comparison(bool value) { + if (value) { + set_bit_field3(bit_field3() | (1 << kUseUserObjectComparison)); + } else { + set_bit_field3(bit_field3() & ~(1 << kUseUserObjectComparison)); + } +} + +bool Map::use_user_object_comparison() { + return ((1 << kUseUserObjectComparison) & bit_field3()) != 0; +} + + void Map::set_named_interceptor_is_fallback(bool value) { if (value) { @@ -3050,6 +3063,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.h b/src/objects.h index edbc47a..e75e9f1 100644 --- a/src/objects.h +++ b/src/objects.h @@ -3724,6 +3724,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(); // Whether the named interceptor is a fallback interceptor or not inline void set_named_interceptor_is_fallback(bool value); @@ -3922,6 +3927,7 @@ class Map: public HeapObject { // Bit positions for bit field 3 static const int kNamedInterceptorIsFallback = 0; static const int kHasExternalResource = 1; + static const int kUseUserObjectComparison = 2; // Layout of the default cache. It holds alternating name and code objects. static const int kCodeCacheEntrySize = 2; @@ -6442,6 +6448,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); @@ -6459,7 +6466,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 827d954..d552ddb 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -6279,6 +6279,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 5e97173..0d754f9 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -146,6 +146,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/top.cc b/src/top.cc index e078ee9..c345383 100644 --- a/src/top.cc +++ b/src/top.cc @@ -68,6 +68,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; } @@ -387,6 +388,10 @@ 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/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc index d923494..10b9b56 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -2443,6 +2443,37 @@ void CompareStub::Generate(MacroAssembler* masm) { __ bind(&ok); } + { + NearLabel 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::kBitField3Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &user_equal); + __ testb(FieldOperand(rcx, Map::kBitField3Offset), + 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); + } + // The compare stub returns a positive, negative, or zero 64-bit integer // value in rax, corresponding to result of comparing the two inputs. // NOTICE! This code is only reached after a smi-fast-case check, so @@ -4471,8 +4502,14 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) { __ CmpObjectType(rax, JS_OBJECT_TYPE, rcx); __ j(not_equal, &miss, not_taken); + __ testb(FieldOperand(rcx, Map::kBitField3Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &miss); __ CmpObjectType(rdx, JS_OBJECT_TYPE, rcx); __ j(not_equal, &miss, not_taken); + __ testb(FieldOperand(rcx, Map::kBitField3Offset), + Immediate(1 << Map::kUseUserObjectComparison)); + __ j(not_zero, &miss); ASSERT(GetCondition() == equal); __ subq(rax, rdx); -- 1.7.6