Commit a83a88cd authored by antonm@chromium.org's avatar antonm@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4616 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 74fb282b
......@@ -979,6 +979,8 @@ class Heap : public AllStatic {
static int MaxObjectSizeInNewSpace() { return kMaxObjectSizeInNewSpace; }
static void ClearJSFunctionResultCaches();
private:
static int reserved_semispace_size_;
static int max_semispace_size_;
......@@ -1193,8 +1195,6 @@ class Heap : public AllStatic {
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);
......
......@@ -6627,14 +6627,120 @@ class DeferredSearchCache: public DeferredCode {
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 @@ void CodeGenerator::GenerateGetFromCache(ZoneList<Expression*>* args) {
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);
......
......@@ -2328,6 +2328,10 @@ class JSFunctionResultCache: public FixedArray {
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();
......
......@@ -4476,22 +4476,142 @@ void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) {
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 @@ void CodeGenerator::GenerateGetFromCache(ZoneList<Expression*>* args) {
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);
......
......@@ -10264,3 +10264,120 @@ TEST(GCCallbacks) {
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");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment