[V8] Add custom object compare callback
authorAaron Kennedy <aaron.kennedy@nokia.com>
Thu, 27 Oct 2011 12:40:00 +0000 (13:40 +0100)
committerQt by Nokia <qt-info@nokia.com>
Thu, 1 Mar 2012 11:03:45 +0000 (12:03 +0100)
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.

Change-Id: Ifb4074058566062accc2be20d43e3febc1089d13
Reviewed-by: Simon Hausmann <simon.hausmann@nokia.com>
13 files changed:
src/3rdparty/v8/include/v8.h
src/3rdparty/v8/src/api.cc
src/3rdparty/v8/src/arm/code-stubs-arm.cc
src/3rdparty/v8/src/factory.cc
src/3rdparty/v8/src/ia32/code-stubs-ia32.cc
src/3rdparty/v8/src/isolate.cc
src/3rdparty/v8/src/isolate.h
src/3rdparty/v8/src/objects-inl.h
src/3rdparty/v8/src/objects.cc
src/3rdparty/v8/src/objects.h
src/3rdparty/v8/src/runtime.cc
src/3rdparty/v8/src/runtime.h
src/3rdparty/v8/src/x64/code-stubs-x64.cc

index c094d08..6baf2b2 100644 (file)
@@ -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<ObjectTemplate> New(Handle<FunctionTemplate> constructor);
@@ -2720,6 +2726,10 @@ typedef void (*FailedAccessCheckCallback)(Local<Object> target,
                                           AccessType type,
                                           Local<Value> data);
 
+// --- User Object Comparisoa nCallback ---
+typedef bool (*UserObjectComparisonCallback)(Local<Object> lhs, 
+                                             Local<Object> 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
index a51fdc4..d31ff9a 100644 (file)
@@ -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 ---
 
 
@@ -5108,6 +5119,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<Value>* objects,
                         size_t length,
                         RetainedObjectInfo* info) {
index b962bc7..1a569b2 100644 (file)
@@ -1573,6 +1573,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, &not_user_equal);
+
+      __ CompareObjectType(r0, r2, r4, JS_OBJECT_TYPE);
+      __ b(ne, &not_user_equal);
+
+      __ CompareObjectType(r1, r3, r4, JS_OBJECT_TYPE);
+      __ b(ne, &not_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, &not_user_equal);
+
+      __ bind(&user_equal);
+
+      __ Push(r0, r1);
+      __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1);
+
+      __ bind(&not_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_);
@@ -6622,10 +6653,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));
index 8c96944..76ca69d 100644 (file)
@@ -1153,6 +1153,7 @@ Handle<JSFunction> Factory::CreateApiFunction(
 
   int internal_field_count = 0;
   bool has_external_resource = false;
+  bool use_user_object_comparison = false;
 
   if (!obj->instance_template()->IsUndefined()) {
     Handle<ObjectTemplateInfo> instance_template =
@@ -1162,6 +1163,8 @@ Handle<JSFunction> 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<JSFunction> 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();
index d32a9e3..b2ff97d 100644 (file)
@@ -4024,6 +4024,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, &not_user_equal);
+    __ test(edx, Immediate(kSmiTagMask));
+    __ j(zero, &not_user_equal);
+
+    __ CmpObjectType(eax, JS_OBJECT_TYPE, ebx);
+    __ j(not_equal, &not_user_equal);
+
+    __ CmpObjectType(edx, JS_OBJECT_TYPE, ecx);
+    __ j(not_equal, &not_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(&not_user_equal);
+
+    __ bind(&user_equal);
+   
+    __ pop(ebx); // Return address.
+    __ push(eax);
+    __ push(edx);
+    __ push(ebx);
+    __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1);
+   
+    __ bind(&not_user_equal);
+  }
+
   // Identical objects can be compared fast, but there are some tricky cases
   // for NaN and undefined.
   {
@@ -6504,8 +6537,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);
index a073af9..36c1dfd 100644 (file)
@@ -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;
index 116b802..17461cf 100644 (file)
@@ -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);
index 4322742..75907c7 100644 (file)
@@ -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)
index acfc357..e13bd6f 100644 (file)
@@ -7674,8 +7674,8 @@ void SharedFunctionInfo::DetachInitialMap() {
   Map* map = reinterpret_cast<Map*>(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
@@ -7695,8 +7695,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);
index 7240b55..0c0dd8f 100644 (file)
@@ -4258,6 +4258,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)
 
@@ -4505,7 +4510,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;
@@ -4524,6 +4529,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;
@@ -7556,6 +7562,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);
 
@@ -7573,7 +7580,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;
 };
 
 
index a9a705f..7c34c36 100644 (file)
@@ -7163,6 +7163,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<JSObject> lhs_handle(lhs);
+      Handle<JSObject> 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);
index 6d3ec67..04f7794 100644 (file)
@@ -158,6 +158,7 @@ namespace internal {
   /* Comparisons */ \
   F(NumberEquals, 2, 1) \
   F(StringEquals, 2, 1) \
+  F(UserObjectEquals, 2, 1) \
   \
   F(NumberCompare, 3, 1) \
   F(SmiLexicographicCompare, 2, 1) \
index a6f8f8e..ae1603b 100644 (file)
@@ -3092,6 +3092,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, &not_user_equal);
+    __ JumpIfSmi(rdx, &not_user_equal);
+
+    __ CmpObjectType(rax, JS_OBJECT_TYPE, rbx);
+    __ j(not_equal, &not_user_equal);
+
+    __ CmpObjectType(rdx, JS_OBJECT_TYPE, rcx);
+    __ j(not_equal, &not_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(&not_user_equal);
+
+    __ bind(&user_equal);
+   
+    __ pop(rbx); // Return address.
+    __ push(rax);
+    __ push(rdx);
+    __ push(rbx);
+    __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1);
+   
+    __ bind(&not_user_equal);
+  }
+
   // Two identical objects are equal unless they are both NaN or undefined.
   {
     Label not_identical;
@@ -5428,8 +5459,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);