From 15e2b05fae59aa4ed7c0974ba296ec8893c4d7f2 Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Thu, 27 Oct 2011 13:40:00 +0100 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 54df40d..974d702 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 --- @@ -5113,6 +5124,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 f5be938..1e1aebd 100644 --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -1569,6 +1569,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_); @@ -6615,10 +6646,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 8c96944..76ca69d 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -1153,6 +1153,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 = @@ -1162,6 +1163,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; @@ -1206,6 +1209,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 8a94a06..e73753e 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -4020,6 +4020,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. { @@ -6497,8 +6530,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 a073af9..36c1dfd 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; top_lookup_result_ = NULL; @@ -729,6 +730,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 5453bf2..9919e83 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -258,6 +258,9 @@ class ThreadLocalTop BASE_EMBEDDED { // Head of the list of live LookupResults. LookupResult* top_lookup_result_; + // 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_; @@ -703,6 +706,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 2e83fb7..13d7591 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -2749,14 +2749,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; } @@ -2786,6 +2786,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) { @@ -3334,6 +3347,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 f5b6bee..6a4eff9 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -7686,8 +7686,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 @@ -7707,8 +7707,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 73e7f8b..9dcacac 100644 --- a/src/objects.h +++ b/src/objects.h @@ -4255,6 +4255,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) @@ -4502,7 +4507,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; @@ -4521,6 +4526,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; @@ -7553,6 +7559,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); @@ -7570,7 +7577,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 5746c20..542e62b 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -7095,6 +7095,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 aada06d..cd36da9 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 f30221f..ff8337f 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -3088,6 +3088,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; @@ -5421,8 +5452,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