[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, 26 Apr 2012 06:56:32 +0000 (08:56 +0200)
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: Icd3419349874ee845684fff8455862da73f5ff2d
Reviewed-by: Kent Hansen <kent.hansen@nokia.com>
14 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/mips/code-stubs-mips.cc
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 a226002..0f23f21 100644 (file)
@@ -2526,6 +2526,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);
@@ -2748,6 +2754,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 ---
 
 /**
@@ -3099,6 +3109,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 d11b98e..4c39e7a 100644 (file)
@@ -1493,6 +1493,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 ---
 
 
@@ -5364,6 +5375,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 02d0555..136d4ab 100644 (file)
@@ -1642,6 +1642,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_);
@@ -6749,10 +6780,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));
@@ -6771,8 +6810,16 @@ void ICCompareStub::GenerateKnownObjects(MacroAssembler* masm) {
   __ ldr(r3, FieldMemOperand(r1, HeapObject::kMapOffset));
   __ cmp(r2, Operand(known_map_));
   __ b(ne, &miss);
+  __ ldrb(r2, FieldMemOperand(r0, Map::kBitField2Offset));
+  __ and_(r2, r2, Operand(1 << Map::kUseUserObjectComparison));
+  __ cmp(r2, Operand(1 << Map::kUseUserObjectComparison));
+  __ b(eq, &miss);
   __ cmp(r3, Operand(known_map_));
   __ b(ne, &miss);
+  __ ldrb(r3, FieldMemOperand(r1, Map::kBitField2Offset));
+  __ and_(r3, r2, Operand(1 << Map::kUseUserObjectComparison));
+  __ cmp(r3, Operand(1 << Map::kUseUserObjectComparison));
+  __ b(eq, &miss);
 
   __ sub(r0, r0, Operand(r1));
   __ Ret();
index 13b2806..ab0bb1f 100644 (file)
@@ -1220,6 +1220,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 =
@@ -1229,6 +1230,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;
@@ -1273,6 +1276,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 b46b428..2568dae 100644 (file)
@@ -4261,6 +4261,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.
   {
@@ -6750,8 +6783,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);
@@ -6772,8 +6811,14 @@ void ICCompareStub::GenerateKnownObjects(MacroAssembler* masm) {
   __ mov(ebx, FieldOperand(edx, HeapObject::kMapOffset));
   __ cmp(ecx, known_map_);
   __ j(not_equal, &miss, Label::kNear);
+  __ test_b(FieldOperand(ecx, Map::kBitField2Offset),
+            1 << Map::kUseUserObjectComparison);
+  __ j(not_zero, &miss, Label::kNear);
   __ cmp(ebx, known_map_);
   __ j(not_equal, &miss, Label::kNear);
+  __ test_b(FieldOperand(ebx, Map::kBitField2Offset),
+            1 << Map::kUseUserObjectComparison);
+  __ j(not_zero, &miss, Label::kNear);
 
   __ sub(eax, edx);
   __ ret(0);
index bf9b345..535ffb1 100644 (file)
@@ -97,6 +97,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;
@@ -743,6 +744,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 2ff1318..9f964a0 100644 (file)
@@ -273,6 +273,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_;
 
@@ -702,6 +705,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 b8595a7..381d193 100644 (file)
@@ -1716,6 +1716,46 @@ 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.
+  {
+    // This is optimized for reading the code and not benchmarked for
+    // speed or amount of instructions. The code is not ordered for speed
+    // or anything like this
+    Label miss, user_compare;
+
+    // No global compare if both operands are SMIs
+    __ And(a2, a1, Operand(a0));
+    __ JumpIfSmi(a2, &miss);
+
+
+    // We need to check if lhs and rhs are both objects, if not we are
+    // jumping out of the function. We will keep the 'map' in t0 (lhs) and
+    // t1 (rhs) for later usage.
+    __ GetObjectType(a0, t0, a3);
+    __ Branch(&miss, ne, a3, Operand(JS_OBJECT_TYPE));
+
+    __ GetObjectType(a1, t1, a3);
+    __ Branch(&miss, ne, a3, Operand(JS_OBJECT_TYPE));
+
+    // Check if the UseUserComparison flag is set by using the map of t0 for lhs
+    __ lbu(t0, FieldMemOperand(t0, Map::kBitField2Offset));
+    __ And(t0, t0, Operand(1 << Map::kUseUserObjectComparison));
+    __ Branch(&user_compare, eq, t0, Operand(1 << Map::kUseUserObjectComparison));
+
+
+    // Check if the UseUserComparison flag is _not_ set by using the map of t1 for
+    // rhs and then jump to the miss label.
+    __ lbu(t1, FieldMemOperand(t1, Map::kBitField2Offset));
+    __ And(t1, t1, Operand(1 << Map::kUseUserObjectComparison));
+    __ Branch(&miss, ne, t1, Operand(1 << Map::kUseUserObjectComparison));
+
+    // Invoke the runtime function here
+    __ bind(&user_compare);
+    __ Push(a0, a1);
+    __ TailCallRuntime(Runtime::kUserObjectEquals, 2, 1);
+
+    // We exit here without doing anything
+    __ bind(&miss);
+  }
 
   // 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.
@@ -6992,10 +7032,20 @@ void ICCompareStub::GenerateObjects(MacroAssembler* masm) {
   __ And(a2, a1, Operand(a0));
   __ JumpIfSmi(a2, &miss);
 
-  __ GetObjectType(a0, a2, a2);
-  __ Branch(&miss, ne, a2, Operand(JS_OBJECT_TYPE));
-  __ GetObjectType(a1, a2, a2);
-  __ Branch(&miss, ne, a2, Operand(JS_OBJECT_TYPE));
+  // Compare lhs, a2 holds the map, a3 holds the type_reg
+  __ GetObjectType(a0, a2, a3);
+  __ Branch(&miss, ne, a3, Operand(JS_OBJECT_TYPE));
+  __ lbu(a2, FieldMemOperand(a2, Map::kBitField2Offset));
+  __ And(a2, a2, Operand(1 << Map::kUseUserObjectComparison));
+  __ Branch(&miss, eq, a2, Operand(1 << Map::kUseUserObjectComparison));
+
+
+  // Compare rhs, a2 holds the map, a3 holds the type_reg
+  __ GetObjectType(a1, a2, a3);
+  __ Branch(&miss, ne, a3, Operand(JS_OBJECT_TYPE));
+  __ lbu(a2, FieldMemOperand(a2, Map::kBitField2Offset));
+  __ And(a2, a2, Operand(1 << Map::kUseUserObjectComparison));
+  __ Branch(&miss, eq, a2, Operand(1 << Map::kUseUserObjectComparison));
 
   ASSERT(GetCondition() == eq);
   __ Ret(USE_DELAY_SLOT);
@@ -7013,7 +7063,13 @@ void ICCompareStub::GenerateKnownObjects(MacroAssembler* masm) {
   __ lw(a2, FieldMemOperand(a0, HeapObject::kMapOffset));
   __ lw(a3, FieldMemOperand(a1, HeapObject::kMapOffset));
   __ Branch(&miss, ne, a2, Operand(known_map_));
+  __ lbu(a2, FieldMemOperand(a0, Map::kBitField2Offset));
+  __ And(a2, a2, Operand(1 << Map::kUseUserObjectComparison));
+  __ Branch(&miss, eq, a2, Operand(1 << Map::kUseUserObjectComparison));
   __ Branch(&miss, ne, a3, Operand(known_map_));
+  __ lbu(a3, FieldMemOperand(a1, Map::kBitField2Offset));
+  __ And(a3, a3, Operand(1 << Map::kUseUserObjectComparison));
+  __ Branch(&miss, eq, a3, Operand(1 << Map::kUseUserObjectComparison));
 
   __ Ret(USE_DELAY_SLOT);
   __ subu(v0, a0, a1);
index 4098590..f29cf6e 100644 (file)
@@ -2908,14 +2908,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;
 }
 
 
@@ -2945,6 +2945,20 @@ 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) {
     set_bit_field3(bit_field3() | (1 << kNamedInterceptorIsFallback));
@@ -3507,6 +3521,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 3cc4a5f..10241de 100644 (file)
@@ -7914,8 +7914,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
@@ -7935,8 +7935,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 d1cdcb6..73629b8 100644 (file)
@@ -4700,6 +4700,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)
 
@@ -4950,7 +4955,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;
@@ -4969,6 +4974,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;
@@ -8321,6 +8327,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);
 
@@ -8338,7 +8345,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 c6eb0b6..b0f57b1 100644 (file)
@@ -7135,6 +7135,29 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_StringEquals) {
 }
 
 
+RUNTIME_FUNCTION(MaybeObject*, Runtime_UserObjectEquals) {
+  NoHandleAllocation ha;
+  ASSERT(args.length() == 2);
+
+  CONVERT_ARG_CHECKED(JSObject, lhs, 1);
+  CONVERT_ARG_CHECKED(JSObject, rhs, 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 8713663..4db5701 100644 (file)
@@ -156,6 +156,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 a71e5d4..63c44e9 100644 (file)
@@ -3328,6 +3328,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;
@@ -5725,8 +5756,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);
@@ -5746,8 +5783,14 @@ void ICCompareStub::GenerateKnownObjects(MacroAssembler* masm) {
   __ movq(rbx, FieldOperand(rdx, HeapObject::kMapOffset));
   __ Cmp(rcx, known_map_);
   __ j(not_equal, &miss, Label::kNear);
+  __ testb(FieldOperand(rcx, Map::kBitField2Offset),
+           Immediate(1 << Map::kUseUserObjectComparison));
+  __ j(not_zero, &miss, Label::kNear);
   __ Cmp(rbx, known_map_);
   __ j(not_equal, &miss, Label::kNear);
+  __ testb(FieldOperand(rbx, Map::kBitField2Offset),
+           Immediate(1 << Map::kUseUserObjectComparison));
+  __ j(not_zero, &miss, Label::kNear);
 
   __ subq(rax, rdx);
   __ ret(0);