Revision: 4616
Author: [email protected]
Date: Fri May  7 05:48:18 2010
Log: Moving more code to lookup an item from the native cache into code generator.

To bypass expensive invocation of JS functions from C++ and omit runtime
call overhead for searching the cache, more elaborate deferred code is generated.

Review URL: http://codereview.chromium.org/1695007
http://code.google.com/p/v8/source/detail?r=4616

Modified:
 /branches/bleeding_edge/src/heap.h
 /branches/bleeding_edge/src/ia32/codegen-ia32.cc
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/src/x64/codegen-x64.cc
 /branches/bleeding_edge/test/cctest/test-api.cc

=======================================
--- /branches/bleeding_edge/src/heap.h  Thu May  6 02:35:18 2010
+++ /branches/bleeding_edge/src/heap.h  Fri May  7 05:48:18 2010
@@ -978,6 +978,8 @@
static inline bool ShouldBePromoted(Address old_address, int object_size);

   static int MaxObjectSizeInNewSpace() { return kMaxObjectSizeInNewSpace; }
+
+  static void ClearJSFunctionResultCaches();

  private:
   static int reserved_semispace_size_;
@@ -1193,8 +1195,6 @@
                                           HeapObject* target,
                                           int size);

-  static void ClearJSFunctionResultCaches();
-
 #if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING)
   // Record the copy of an object in the NewSpace's statistics.
   static void RecordCopiedObject(HeapObject* obj);
=======================================
--- /branches/bleeding_edge/src/ia32/codegen-ia32.cc Fri May 7 04:55:24 2010 +++ /branches/bleeding_edge/src/ia32/codegen-ia32.cc Fri May 7 05:48:18 2010
@@ -6627,14 +6627,120 @@
   virtual void Generate();

  private:
-  Register dst_, cache_, key_;
+  Register dst_;    // on invocation Smi index of finger, on exit
+                    // holds value being looked up.
+  Register cache_;  // instance of JSFunctionResultCache.
+  Register key_;    // key being looked up.
 };
+
+
+// Return a position of the element at |index_as_smi| + |additional_offset|
+// in FixedArray pointer to which is held in |array|. |index_as_smi| is Smi.
+static Operand ArrayElement(Register array,
+                            Register index_as_smi,
+                            int additional_offset = 0) {
+  int offset = FixedArray::kHeaderSize + additional_offset * kPointerSize;
+ return FieldOperand(array, index_as_smi, times_half_pointer_size, offset);
+}


 void DeferredSearchCache::Generate() {
-  __ push(cache_);
+  Label first_loop, search_further, second_loop, cache_miss;
+
+  // Smi-tagging is equivalent to multiplying by 2.
+  STATIC_ASSERT(kSmiTag == 0);
+  STATIC_ASSERT(kSmiTagSize == 1);
+
+  Smi* kEntrySizeSmi = Smi::FromInt(JSFunctionResultCache::kEntrySize);
+ Smi* kEntriesIndexSmi = Smi::FromInt(JSFunctionResultCache::kEntriesIndex);
+
+  // Check the cache from finger to start of the cache.
+  __ bind(&first_loop);
+  __ sub(Operand(dst_), Immediate(kEntrySizeSmi));
+  __ cmp(Operand(dst_), Immediate(kEntriesIndexSmi));
+  __ j(less, &search_further);
+
+  __ cmp(key_, ArrayElement(cache_, dst_));
+  __ j(not_equal, &first_loop);
+
+  __ mov(FieldOperand(cache_, JSFunctionResultCache::kFingerOffset), dst_);
+  __ mov(dst_, ArrayElement(cache_, dst_, 1));
+  __ jmp(exit_label());
+
+  __ bind(&search_further);
+
+  // Check the cache from end of cache up to finger.
+ __ mov(dst_, FieldOperand(cache_, JSFunctionResultCache::kCacheSizeOffset));
+
+  __ bind(&second_loop);
+  __ sub(Operand(dst_), Immediate(kEntrySizeSmi));
+    // Consider prefetching into some reg.
+  __ cmp(dst_, FieldOperand(cache_, JSFunctionResultCache::kFingerOffset));
+  __ j(less_equal, &cache_miss);
+
+  __ cmp(key_, ArrayElement(cache_, dst_));
+  __ j(not_equal, &second_loop);
+
+  __ mov(FieldOperand(cache_, JSFunctionResultCache::kFingerOffset), dst_);
+  __ mov(dst_, ArrayElement(cache_, dst_, 1));
+  __ jmp(exit_label());
+
+  __ bind(&cache_miss);
+  __ push(cache_);  // store a reference to cache
+  __ push(key_);  // store a key
+  Handle<Object> receiver(Top::global_context()->global());
+  __ push(Immediate(receiver));
   __ push(key_);
-  __ CallRuntime(Runtime::kGetFromCache, 2);
+  // On ia32 function must be in edi.
+  __ mov(edi, FieldOperand(cache_, JSFunctionResultCache::kFactoryOffset));
+  ParameterCount expected(1);
+  __ InvokeFunction(edi, expected, CALL_FUNCTION);
+
+  // Find a place to put new cached value into.
+  Label add_new_entry, update_cache;
+  __ mov(ecx, Operand(esp, kPointerSize));  // restore the cache
+  // Possible optimization: cache size is constant for the given cache
+  // so technically we could use a constant here.  However, if we have
+  // cache miss this optimization would hardly matter much.
+
+  // Check if we could add new entry to cache.
+  __ mov(ebx, FieldOperand(ecx, FixedArray::kLengthOffset));
+  __ SmiTag(ebx);
+  __ cmp(ebx, FieldOperand(ecx, JSFunctionResultCache::kCacheSizeOffset));
+  __ j(greater, &add_new_entry);
+
+  // Check if we could evict entry after finger.
+  __ mov(edx, FieldOperand(ecx, JSFunctionResultCache::kFingerOffset));
+  __ add(Operand(edx), Immediate(kEntrySizeSmi));
+  __ cmp(ebx, Operand(edx));
+  __ j(greater, &update_cache);
+
+  // Need to wrap over the cache.
+  __ mov(edx, Immediate(kEntriesIndexSmi));
+  __ jmp(&update_cache);
+
+  __ bind(&add_new_entry);
+  __ mov(edx, FieldOperand(ecx, JSFunctionResultCache::kCacheSizeOffset));
+  __ lea(ebx, Operand(edx, JSFunctionResultCache::kEntrySize << 1));
+  __ mov(FieldOperand(ecx, JSFunctionResultCache::kCacheSizeOffset), ebx);
+
+  // Update the cache itself.
+  // edx holds the index.
+  __ bind(&update_cache);
+  __ pop(ebx);  // restore the key
+  __ mov(FieldOperand(ecx, JSFunctionResultCache::kFingerOffset), edx);
+  // Store key.
+  __ mov(ArrayElement(ecx, edx), ebx);
+  __ RecordWrite(ecx, 0, ebx, edx);
+
+  // Store value.
+  __ pop(ecx);  // restore the cache.
+  __ mov(edx, FieldOperand(ecx, JSFunctionResultCache::kFingerOffset));
+  __ add(Operand(edx), Immediate(Smi::FromInt(1)));
+  __ mov(ebx, eax);
+  __ mov(ArrayElement(ecx, edx), ebx);
+  __ RecordWrite(ecx, 0, ebx, edx);
+
   if (!dst_.is(eax)) {
     __ mov(dst_, eax);
   }
@@ -6676,21 +6782,14 @@
                                                           cache.reg(),
                                                           key.reg());

-  const int kFingerOffset =
-      FixedArray::OffsetOfElementAt(JSFunctionResultCache::kFingerIndex);
   // tmp.reg() now holds finger offset as a smi.
   ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
-  __ mov(tmp.reg(), FieldOperand(cache.reg(), kFingerOffset));
-  __ cmp(key.reg(), FieldOperand(cache.reg(),
-                                 tmp.reg(),  // as smi
-                                 times_half_pointer_size,
-                                 FixedArray::kHeaderSize));
+  __ mov(tmp.reg(), FieldOperand(cache.reg(),
+                    JSFunctionResultCache::kFingerOffset));
+  __ cmp(key.reg(), ArrayElement(cache.reg(), tmp.reg()));
   deferred->Branch(not_equal);

-  __ mov(tmp.reg(), FieldOperand(cache.reg(),
-                                 tmp.reg(),  // as smi
-                                 times_half_pointer_size,
-                                 kPointerSize + FixedArray::kHeaderSize));
+  __ mov(tmp.reg(), ArrayElement(cache.reg(), tmp.reg(), 1));

   deferred->BindExit();
   frame_->Push(&tmp);
=======================================
--- /branches/bleeding_edge/src/objects.h       Thu May  6 06:21:53 2010
+++ /branches/bleeding_edge/src/objects.h       Fri May  7 05:48:18 2010
@@ -2328,6 +2328,10 @@

   static const int kEntrySize = 2;  // key + value

+  static const int kFactoryOffset = kHeaderSize;
+  static const int kFingerOffset = kFactoryOffset + kPointerSize;
+  static const int kCacheSizeOffset = kFingerOffset + kPointerSize;
+
   inline void MakeZeroSize();
   inline void Clear();

=======================================
--- /branches/bleeding_edge/src/x64/codegen-x64.cc      Fri May  7 04:25:29 2010
+++ /branches/bleeding_edge/src/x64/codegen-x64.cc      Fri May  7 05:48:18 2010
@@ -4476,22 +4476,142 @@

 class DeferredSearchCache: public DeferredCode {
  public:
-  DeferredSearchCache(Register dst, Register cache, Register key)
-      : dst_(dst), cache_(cache), key_(key) {
+  DeferredSearchCache(Register dst,
+                      Register cache,
+                      Register key,
+                      Register scratch)
+      : dst_(dst), cache_(cache), key_(key), scratch_(scratch) {
     set_comment("[ DeferredSearchCache");
   }

   virtual void Generate();

  private:
-  Register dst_, cache_, key_;
+  Register dst_;    // on invocation index of finger (as Smi), on exit
+                    // holds value being looked up.
+  Register cache_;  // instance of JSFunctionResultCache.
+  Register key_;    // key being looked up.
+  Register scratch_;
 };
+
+
+// Return a position of the element at |index| + |additional_offset|
+// in FixedArray pointer to which is held in |array|.  |index| is int32.
+static Operand ArrayElement(Register array,
+                            Register index,
+                            int additional_offset = 0) {
+  int offset = FixedArray::kHeaderSize + additional_offset * kPointerSize;
+  return FieldOperand(array, index, times_pointer_size, offset);
+}


 void DeferredSearchCache::Generate() {
-  __ push(cache_);
+  Label first_loop, search_further, second_loop, cache_miss;
+
+ Immediate kEntriesIndexImm = Immediate(JSFunctionResultCache::kEntriesIndex);
+  Immediate kEntrySizeImm = Immediate(JSFunctionResultCache::kEntrySize);
+
+  __ SmiToInteger32(dst_, dst_);
+  // Check the cache from finger to start of the cache.
+  __ bind(&first_loop);
+  __ subq(dst_, kEntrySizeImm);
+  __ cmpq(dst_, kEntriesIndexImm);
+  __ j(less, &search_further);
+
+  __ cmpq(ArrayElement(cache_, dst_), key_);
+  __ j(not_equal, &first_loop);
+
+  __ Integer32ToSmi(scratch_, dst_);
+ __ movq(FieldOperand(cache_, JSFunctionResultCache::kFingerOffset), scratch_);
+  __ movq(dst_, ArrayElement(cache_, dst_, 1));
+  __ jmp(exit_label());
+
+  __ bind(&search_further);
+
+  // Check the cache from end of cache up to finger.
+ __ movq(dst_, FieldOperand(cache_, JSFunctionResultCache::kCacheSizeOffset)); + __ movq(scratch_, FieldOperand(cache_, JSFunctionResultCache::kFingerOffset));
+  __ SmiToInteger32(dst_, dst_);
+  __ SmiToInteger32(scratch_, scratch_);
+
+  __ bind(&second_loop);
+  __ subq(dst_, kEntrySizeImm);
+  __ cmpq(dst_, scratch_);
+  __ j(less_equal, &cache_miss);
+
+  __ cmpq(ArrayElement(cache_, dst_), key_);
+  __ j(not_equal, &second_loop);
+
+  __ Integer32ToSmi(scratch_, dst_);
+ __ movq(FieldOperand(cache_, JSFunctionResultCache::kFingerOffset), scratch_);
+  __ movq(dst_, ArrayElement(cache_, dst_, 1));
+  __ jmp(exit_label());
+
+  __ bind(&cache_miss);
+  __ push(cache_);  // store a reference to cache
+  __ push(key_);  // store a key
+  Handle<Object> receiver(Top::global_context()->global());
+  __ Push(receiver);
   __ push(key_);
-  __ CallRuntime(Runtime::kGetFromCache, 2);
+  // On x64 function must be in rdi.
+ __ movq(rdi, FieldOperand(cache_, JSFunctionResultCache::kFactoryOffset));
+  ParameterCount expected(1);
+  __ InvokeFunction(rdi, expected, CALL_FUNCTION);
+
+  // Find a place to put new cached value into.
+  Label add_new_entry, update_cache;
+  __ movq(rcx, Operand(rsp, kPointerSize));  // restore the cache
+  // Possible optimization: cache size is constant for the given cache
+  // so technically we could use a constant here.  However, if we have
+  // cache miss this optimization would hardly matter much.
+
+  // Check if we could add new entry to cache.
+  __ movl(rbx, FieldOperand(rcx, FixedArray::kLengthOffset));
+  __ movq(r9, FieldOperand(rcx, JSFunctionResultCache::kCacheSizeOffset));
+  __ SmiToInteger32(r9, r9);
+  __ cmpq(rbx, r9);
+  __ j(greater, &add_new_entry);
+
+  // Check if we could evict entry after finger.
+  __ movq(rdx, FieldOperand(rcx, JSFunctionResultCache::kFingerOffset));
+  __ SmiToInteger32(rdx, rdx);
+  __ addq(rdx, kEntrySizeImm);
+  Label forward;
+  __ cmpq(rbx, rdx);
+  __ j(greater, &forward);
+  // Need to wrap over the cache.
+  __ movq(rdx, kEntriesIndexImm);
+  __ bind(&forward);
+  __ Integer32ToSmi(r9, rdx);
+  __ jmp(&update_cache);
+
+  __ bind(&add_new_entry);
+  // r9 holds cache size as int.
+  __ movq(rdx, r9);
+  __ Integer32ToSmi(r9, r9);
+ __ SmiAddConstant(rbx, r9, Smi::FromInt(JSFunctionResultCache::kEntrySize));
+  __ movq(FieldOperand(rcx, JSFunctionResultCache::kCacheSizeOffset), rbx);
+
+  // Update the cache itself.
+  // rdx holds the index as int.
+  // r9 holds the index as smi.
+  __ bind(&update_cache);
+  __ pop(rbx);  // restore the key
+  __ movq(FieldOperand(rcx, JSFunctionResultCache::kFingerOffset), r9);
+  // Store key.
+  __ movq(ArrayElement(rcx, rdx), rbx);
+  __ RecordWrite(rcx, 0, rbx, r9);
+
+  // Store value.
+  __ pop(rcx);  // restore the cache.
+  __ movq(rdx, FieldOperand(rcx, JSFunctionResultCache::kFingerOffset));
+  __ SmiAddConstant(rdx, rdx, Smi::FromInt(1));
+  __ movq(r9, rdx);
+  __ SmiToInteger32(rdx, rdx);
+  __ movq(rbx, rax);
+  __ movq(ArrayElement(rcx, rdx), rbx);
+  __ RecordWrite(rcx, 0, rbx, r9);
+
   if (!dst_.is(rax)) {
     __ movq(dst_, rax);
   }
@@ -4529,27 +4649,28 @@
   Result tmp = allocator()->Allocate();
   ASSERT(tmp.is_valid());

+  Result scratch = allocator()->Allocate();
+  ASSERT(scratch.is_valid());
+
   DeferredSearchCache* deferred = new DeferredSearchCache(tmp.reg(),
                                                           cache.reg(),
-                                                          key.reg());
+                                                          key.reg(),
+                                                          scratch.reg());

   const int kFingerOffset =
       FixedArray::OffsetOfElementAt(JSFunctionResultCache::kFingerIndex);
   // tmp.reg() now holds finger offset as a smi.
-  ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
   __ movq(tmp.reg(), FieldOperand(cache.reg(), kFingerOffset));
   SmiIndex index =
       masm()->SmiToIndex(kScratchRegister, tmp.reg(), kPointerSizeLog2);
   __ cmpq(key.reg(), FieldOperand(cache.reg(),
-                                  index.reg,
-                                  index.scale,
+                                  index.reg, index.scale,
                                   FixedArray::kHeaderSize));
+  // Do NOT alter index.reg or tmp.reg() before cmpq below.
   deferred->Branch(not_equal);
-
   __ movq(tmp.reg(), FieldOperand(cache.reg(),
-                                  index.reg,
-                                  index.scale,
-                                  kPointerSize + FixedArray::kHeaderSize));
+                                  index.reg, index.scale,
+                                  FixedArray::kHeaderSize + kPointerSize));

   deferred->BindExit();
   frame_->Push(&tmp);
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc     Thu May  6 04:05:50 2010
+++ /branches/bleeding_edge/test/cctest/test-api.cc     Fri May  7 05:48:18 2010
@@ -10264,3 +10264,120 @@
   CHECK_EQ(2, prologue_call_count_second);
   CHECK_EQ(2, epilogue_call_count_second);
 }
+
+
+THREADED_TEST(AddToJSFunctionResultCache) {
+  i::FLAG_allow_natives_syntax = true;
+  v8::HandleScope scope;
+
+  LocalContext context;
+
+  const char* code =
+      "(function() {"
+      "  var key0 = 'a';"
+      "  var key1 = 'b';"
+      "  var r0 = %_GetFromCache(0, key0);"
+      "  var r1 = %_GetFromCache(0, key1);"
+      "  var r0_ = %_GetFromCache(0, key0);"
+      "  if (r0 !== r0_)"
+ " return 'Different results for ' + key0 + ': ' + r0 + ' vs. ' + r0_;"
+      "  var r1_ = %_GetFromCache(0, key1);"
+      "  if (r1 !== r1_)"
+ " return 'Different results for ' + key1 + ': ' + r1 + ' vs. ' + r1_;"
+      "  return 'PASSED';"
+      "})()";
+  v8::internal::Heap::ClearJSFunctionResultCaches();
+  ExpectString(code, "PASSED");
+}
+
+
+static const int k0CacheSize = 16;
+
+THREADED_TEST(FillJSFunctionResultCache) {
+  i::FLAG_allow_natives_syntax = true;
+  v8::HandleScope scope;
+
+  LocalContext context;
+
+  const char* code =
+      "(function() {"
+      "  var k = 'a';"
+      "  var r = %_GetFromCache(0, k);"
+      "  for (var i = 0; i < 16; i++) {"
+      "    %_GetFromCache(0, 'a' + i);"
+      "  };"
+      "  if (r === %_GetFromCache(0, k))"
+      "    return 'FAILED: k0CacheSize is too small';"
+      "  return 'PASSED';"
+      "})()";
+  v8::internal::Heap::ClearJSFunctionResultCaches();
+  ExpectString(code, "PASSED");
+}
+
+
+THREADED_TEST(RoundRobinGetFromCache) {
+  i::FLAG_allow_natives_syntax = true;
+  v8::HandleScope scope;
+
+  LocalContext context;
+
+  const char* code =
+      "(function() {"
+      "  var keys = [];"
+      "  for (var i = 0; i < 16; i++) keys.push(i);"
+      "  var values = [];"
+ " for (var i = 0; i < 16; i++) values[i] = %_GetFromCache(0, keys[i]);"
+      "  for (var i = 0; i < 16; i++) {"
+      "    var v = %_GetFromCache(0, keys[i]);"
+      "    if (v !== values[i])"
+      "      return 'Wrong value for ' + "
+      "          keys[i] + ': ' + v + ' vs. ' + values[i];"
+      "  };"
+      "  return 'PASSED';"
+      "})()";
+  v8::internal::Heap::ClearJSFunctionResultCaches();
+  ExpectString(code, "PASSED");
+}
+
+
+THREADED_TEST(ReverseGetFromCache) {
+  i::FLAG_allow_natives_syntax = true;
+  v8::HandleScope scope;
+
+  LocalContext context;
+
+  const char* code =
+      "(function() {"
+      "  var keys = [];"
+      "  for (var i = 0; i < 16; i++) keys.push(i);"
+      "  var values = [];"
+ " for (var i = 0; i < 16; i++) values[i] = %_GetFromCache(0, keys[i]);"
+      "  for (var i = 15; i >= 16; i--) {"
+      "    var v = %_GetFromCache(0, keys[i]);"
+      "    if (v !== values[i])"
+      "      return 'Wrong value for ' + "
+      "          keys[i] + ': ' + v + ' vs. ' + values[i];"
+      "  };"
+      "  return 'PASSED';"
+      "})()";
+  v8::internal::Heap::ClearJSFunctionResultCaches();
+  ExpectString(code, "PASSED");
+}
+
+
+THREADED_TEST(TestEviction) {
+  i::FLAG_allow_natives_syntax = true;
+  v8::HandleScope scope;
+
+  LocalContext context;
+
+  const char* code =
+      "(function() {"
+      "  for (var i = 0; i < 2*16; i++) {"
+      "    %_GetFromCache(0, 'a' + i);"
+      "  };"
+      "  return 'PASSED';"
+      "})()";
+  v8::internal::Heap::ClearJSFunctionResultCaches();
+  ExpectString(code, "PASSED");
+}

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to