Chromium Code Reviews| Index: src/ia32/stub-cache-ia32.cc |
| diff --git a/src/ia32/stub-cache-ia32.cc b/src/ia32/stub-cache-ia32.cc |
| index 7acf81c9495de8b0e716c518ad08a638088df5c8..a65b4fe7e637e71b696e2c2c4ffb401d29224eea 100644 |
| --- a/src/ia32/stub-cache-ia32.cc |
| +++ b/src/ia32/stub-cache-ia32.cc |
| @@ -479,88 +479,352 @@ class LoadInterceptorCompiler BASE_EMBEDDED { |
| }; |
| +// Holds information about possible function call optimizations. |
| +class CallOptimization BASE_EMBEDDED { |
| + public: |
| + explicit CallOptimization(LookupResult* lookup) |
| + : constant_function_(NULL), |
| + is_simple_api_call_(false), |
| + expected_receiver_type_(NULL), |
| + api_call_info_(NULL) { |
| + if (!lookup->IsValid() || !lookup->IsCacheable()) return; |
| + |
| + // We only optimize constant function calls. |
| + if (lookup->type() != CONSTANT_FUNCTION) return; |
| + |
| + Initialize(lookup->GetConstantFunction()); |
| + } |
| + |
| + explicit CallOptimization(JSFunction* function) { |
| + Initialize(function); |
| + } |
| + |
| + bool is_constant_call() const { |
| + return constant_function_ != NULL; |
| + } |
| + |
| + JSFunction* constant_function() const { |
| + ASSERT(constant_function_ != NULL); |
| + return constant_function_; |
| + } |
| + |
| + bool is_simple_api_call() const { |
| + return is_simple_api_call_; |
| + } |
| + |
| + FunctionTemplateInfo* expected_receiver_type() const { |
| + ASSERT(is_simple_api_call_); |
| + return expected_receiver_type_; |
| + } |
| + |
| + CallHandlerInfo* api_call_info() const { |
| + ASSERT(is_simple_api_call_); |
| + return api_call_info_; |
| + } |
| + |
| + // Returns the depth of the object having the expected type in the |
| + // prototype chain between the two arguments. |
| + int GetPrototypeDepthOfExpectedType(JSObject* object, |
| + JSObject* holder) const { |
| + ASSERT(is_simple_api_call_); |
| + if (expected_receiver_type_ == NULL) return 0; |
| + int depth = 0; |
| + while (object != holder) { |
| + if (object->IsInstanceOf(expected_receiver_type_)) return depth; |
| + object = JSObject::cast(object->GetPrototype()); |
| + ++depth; |
| + } |
| + if (holder->IsInstanceOf(expected_receiver_type_)) return depth; |
| + return kInvalidProtoDepth; |
| + } |
| + |
| + private: |
| + void Initialize(JSFunction* function) { |
| + if (!function->is_compiled()) return; |
| + |
| + constant_function_ = function; |
| + is_simple_api_call_ = false; |
| + |
| + AnalyzePossibleApiFunction(function); |
| + } |
| + |
| + // Determines whether the given function can be called using the |
| + // fast api call builtin. |
| + void AnalyzePossibleApiFunction(JSFunction* function) { |
| + SharedFunctionInfo* sfi = function->shared(); |
| + if (sfi->function_data()->IsUndefined()) return; |
| + FunctionTemplateInfo* info = |
| + FunctionTemplateInfo::cast(sfi->function_data()); |
| + |
| + // Require a C++ callback. |
| + if (info->call_code()->IsUndefined()) return; |
| + api_call_info_ = CallHandlerInfo::cast(info->call_code()); |
| + |
| + // Accept signatures that either have no restrictions at all or |
| + // only have restrictions on the receiver. |
| + if (!info->signature()->IsUndefined()) { |
| + SignatureInfo* signature = SignatureInfo::cast(info->signature()); |
| + if (!signature->args()->IsUndefined()) return; |
| + if (!signature->receiver()->IsUndefined()) { |
| + expected_receiver_type_ = |
| + FunctionTemplateInfo::cast(signature->receiver()); |
| + } |
| + } |
| + |
| + is_simple_api_call_ = true; |
| + } |
| + |
| + JSFunction* constant_function_; |
| + bool is_simple_api_call_; |
| + FunctionTemplateInfo* expected_receiver_type_; |
| + CallHandlerInfo* api_call_info_; |
| +}; |
| + |
| + |
| +// Reserves space for the extra arguments to FastHandleApiCall in the |
| +// caller's frame. |
| +// |
| +// These arguments are set by CheckPrototypes and GenerateFastApiCall. |
| +static void ReserveSpaceForFastApiCall(MacroAssembler* masm, Register scratch) { |
| + // ----------- S t a t e ------------- |
| + // -- esp[0] : return address |
| + // -- esp[4] : last argument in the internal frame of the caller |
| + // ----------------------------------- |
| + __ pop(scratch); |
| + __ push(Immediate(Smi::FromInt(0))); |
| + __ push(Immediate(Smi::FromInt(0))); |
| + __ push(Immediate(Smi::FromInt(0))); |
| + __ push(Immediate(Smi::FromInt(0))); |
| + __ push(scratch); |
| +} |
| + |
| + |
| +// Undoes the effects of ReserveSpaceForFastApiCall. |
| +static void FreeSpaceForFastApiCall(MacroAssembler* masm, Register scratch) { |
| + // ----------- S t a t e ------------- |
| + // -- esp[0] : return address |
| + // -- esp[4] : last fast api call extra argument |
| + // -- ... |
| + // -- esp[16] : first fast api call extra argument |
| + // -- esp[20] : last argument in the internal frame |
| + // ----------------------------------- |
| + __ pop(scratch); |
| + __ add(Operand(esp), Immediate(kPointerSize * 4)); |
| + __ push(scratch); |
| +} |
| + |
| + |
| +// Generates call to FastHandleApiCall builtin. |
| +static void GenerateFastApiCall(MacroAssembler* masm, |
| + const CallOptimization& optimization, |
| + int argc) { |
| + // ----------- S t a t e ------------- |
| + // -- esp[0] : return address |
| + // -- esp[4] : object passing the type check |
| + // (last fast api call extra argument, |
| + // set by CheckPrototypes) |
| + // -- esp[8] : api call data |
| + // -- esp[12] : api callback |
| + // -- esp[16] : api function |
| + // (first fast api call extra argument) |
| + // -- esp[20] : last argument |
| + // -- ... |
| + // -- esp[(argc + 5) * 4] : first argument |
| + // -- esp[(argc + 6) * 4] : receiver |
| + // ----------------------------------- |
| + |
| + // Get the function and setup the context. |
| + JSFunction* function = optimization.constant_function(); |
| + __ mov(edi, Immediate(Handle<JSFunction>(function))); |
| + __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
| + |
| + // Pass the additional arguments FastHandleApiCall expects. |
| + __ mov(Operand(esp, 4 * kPointerSize), edi); |
| + bool info_loaded = false; |
| + Object* callback = optimization.api_call_info()->callback(); |
| + if (Heap::InNewSpace(callback)) { |
| + info_loaded = true; |
| + __ mov(ecx, Handle<CallHandlerInfo>(optimization.api_call_info())); |
| + __ mov(ebx, FieldOperand(ecx, CallHandlerInfo::kCallbackOffset)); |
| + __ mov(Operand(esp, 3 * kPointerSize), ebx); |
| + } else { |
| + __ mov(Operand(esp, 3 * kPointerSize), Immediate(Handle<Object>(callback))); |
| + } |
| + Object* call_data = optimization.api_call_info()->data(); |
| + if (Heap::InNewSpace(call_data)) { |
| + if (!info_loaded) { |
| + __ mov(ecx, Handle<CallHandlerInfo>(optimization.api_call_info())); |
| + } |
| + __ mov(ebx, FieldOperand(ecx, CallHandlerInfo::kDataOffset)); |
| + __ mov(Operand(esp, 2 * kPointerSize), ebx); |
| + } else { |
| + __ mov(Operand(esp, 2 * kPointerSize), |
| + Immediate(Handle<Object>(call_data))); |
| + } |
| + |
| + // Set the number of arguments. |
| + __ mov(eax, Immediate(argc + 4)); |
| + |
| + // Jump to the fast api call builtin (tail call). |
| + Handle<Code> code = Handle<Code>( |
| + Builtins::builtin(Builtins::FastHandleApiCall)); |
| + ParameterCount expected(0); |
| + __ InvokeCode(code, expected, expected, |
| + RelocInfo::CODE_TARGET, JUMP_FUNCTION); |
| +} |
| + |
| + |
| class CallInterceptorCompiler BASE_EMBEDDED { |
| public: |
| - CallInterceptorCompiler(const ParameterCount& arguments, Register name) |
| - : arguments_(arguments), argc_(arguments.immediate()), name_(name) {} |
| + CallInterceptorCompiler(StubCompiler* stub_compiler, |
| + const ParameterCount& arguments, |
| + Register name) |
| + : stub_compiler_(stub_compiler), |
| + arguments_(arguments), |
| + argc_(arguments.immediate()), |
| + name_(name) {} |
| + |
| + void Compile(MacroAssembler* masm, |
| + JSObject* object, |
| + JSObject* holder, |
| + String* name, |
| + LookupResult* lookup, |
| + Register receiver, |
| + Register scratch1, |
| + Register scratch2, |
| + Label* miss) { |
| + ASSERT(holder->HasNamedInterceptor()); |
| + ASSERT(!holder->GetNamedInterceptor()->getter()->IsUndefined()); |
| + |
| + // Check that the receiver isn't a smi. |
| + __ test(receiver, Immediate(kSmiTagMask)); |
| + __ j(zero, miss, not_taken); |
| + |
| + CallOptimization optimization(lookup); |
| + |
| + if (optimization.is_constant_call() && |
| + !Top::CanHaveSpecialFunctions(holder)) { |
| + CompileCacheable(masm, |
| + object, |
| + receiver, |
| + scratch1, |
| + scratch2, |
| + holder, |
| + lookup, |
| + name, |
| + optimization, |
| + miss); |
| + } else { |
| + CompileRegular(masm, |
| + object, |
| + receiver, |
| + scratch1, |
| + scratch2, |
| + name, |
| + holder, |
| + miss); |
| + } |
| + } |
| + private: |
| void CompileCacheable(MacroAssembler* masm, |
| - StubCompiler* stub_compiler, |
| + JSObject* object, |
| Register receiver, |
| - Register holder, |
| Register scratch1, |
| Register scratch2, |
| JSObject* holder_obj, |
| LookupResult* lookup, |
| String* name, |
| + const CallOptimization& optimization, |
| Label* miss_label) { |
| - JSFunction* function = 0; |
| - bool optimize = false; |
| - // So far the most popular case for failed interceptor is |
| - // CONSTANT_FUNCTION sitting below. |
| - if (lookup->type() == CONSTANT_FUNCTION) { |
| - function = lookup->GetConstantFunction(); |
| - // JSArray holder is a special case for call constant function |
| - // (see the corresponding code). |
| - if (function->is_compiled() && !holder_obj->IsJSArray()) { |
| - optimize = true; |
| + ASSERT(optimization.is_constant_call()); |
| + |
| + int depth1 = kInvalidProtoDepth; |
| + int depth2 = kInvalidProtoDepth; |
| + bool can_do_fast_api_call = false; |
| + if (optimization.is_simple_api_call() && |
| + !lookup->holder()->IsGlobalObject()) { |
| + depth1 = optimization.GetPrototypeDepthOfExpectedType(object, holder_obj); |
| + if (depth1 == kInvalidProtoDepth) { |
| + depth2 = optimization.GetPrototypeDepthOfExpectedType(holder_obj, |
| + lookup->holder()); |
| } |
| + can_do_fast_api_call = (depth1 != kInvalidProtoDepth) || |
| + (depth2 != kInvalidProtoDepth); |
| } |
| - if (!optimize) { |
| - CompileRegular(masm, receiver, holder, scratch2, holder_obj, miss_label); |
| - return; |
| + __ IncrementCounter(&Counters::call_const_interceptor, 1); |
| + |
| + if (can_do_fast_api_call) { |
| + __ IncrementCounter(&Counters::call_const_interceptor_fast_api, 1); |
| + ReserveSpaceForFastApiCall(masm, scratch1); |
| } |
| - __ EnterInternalFrame(); |
| - __ push(holder); // Save the holder. |
| - __ push(name_); // Save the name. |
| + Label miss; |
| + Register holder = |
| + stub_compiler_->CheckPrototypes(object, receiver, holder_obj, |
| + scratch1, scratch2, name, |
| + depth1, &miss); |
| - CompileCallLoadPropertyWithInterceptor(masm, |
| - receiver, |
| - holder, |
| - name_, |
| - holder_obj); |
| + Label regular_invoke; |
| + LoadWithInterceptor(masm, receiver, holder, holder_obj, ®ular_invoke); |
| - __ pop(name_); // Restore the name. |
| - __ pop(receiver); // Restore the holder. |
| - __ LeaveInternalFrame(); |
| + // Generate code for the failed interceptor case. |
| + |
| + // Check the lookup is still valid. |
| + stub_compiler_->CheckPrototypes(holder_obj, receiver, |
| + lookup->holder(), |
| + scratch1, scratch2, name, |
| + depth2, &miss); |
| - __ cmp(eax, Factory::no_interceptor_result_sentinel()); |
| - Label invoke; |
| - __ j(not_equal, &invoke); |
| - |
| - stub_compiler->CheckPrototypes(holder_obj, receiver, |
| - lookup->holder(), scratch1, |
| - scratch2, |
| - name, |
| - miss_label); |
| if (lookup->holder()->IsGlobalObject()) { |
| + ASSERT(!can_do_fast_api_call); |
| __ mov(edx, Operand(esp, (argc_ + 1) * kPointerSize)); |
| __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalReceiverOffset)); |
| __ mov(Operand(esp, (argc_ + 1) * kPointerSize), edx); |
| } |
| - ASSERT(function->is_compiled()); |
| - // Get the function and setup the context. |
| - __ mov(edi, Immediate(Handle<JSFunction>(function))); |
| - __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
| + if (can_do_fast_api_call) { |
| + GenerateFastApiCall(masm, optimization, argc_); |
| + } else { |
| + // Get the function and setup the context. |
| + JSFunction* function = optimization.constant_function(); |
| + __ mov(edi, Immediate(Handle<JSFunction>(function))); |
| + __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
| + |
| + // Jump to the cached code (tail call). |
| + ASSERT(function->is_compiled()); |
| + Handle<Code> code(function->code()); |
| + ParameterCount expected(function->shared()->formal_parameter_count()); |
| + __ InvokeCode(code, expected, arguments_, |
| + RelocInfo::CODE_TARGET, JUMP_FUNCTION); |
| + } |
| - // Jump to the cached code (tail call). |
| - Handle<Code> code(function->code()); |
| - ParameterCount expected(function->shared()->formal_parameter_count()); |
| - __ InvokeCode(code, expected, arguments_, |
| - RelocInfo::CODE_TARGET, JUMP_FUNCTION); |
| + __ bind(&miss); |
|
antonm
2010/02/08 12:07:38
feel free to ignore, but in case when !can_do_fast
Vitaly Repeshko
2010/02/09 16:15:24
Done.
|
| + if (can_do_fast_api_call) { |
| + FreeSpaceForFastApiCall(masm, scratch1); |
| + } |
| + __ jmp(miss_label); |
| - __ bind(&invoke); |
| + __ bind(®ular_invoke); |
| + if (can_do_fast_api_call) { |
| + FreeSpaceForFastApiCall(masm, scratch1); |
| + } |
| } |
| void CompileRegular(MacroAssembler* masm, |
| + JSObject* object, |
| Register receiver, |
| - Register holder, |
| - Register scratch, |
| + Register scratch1, |
| + Register scratch2, |
| + String* name, |
| JSObject* holder_obj, |
| Label* miss_label) { |
| + Register holder = |
| + stub_compiler_->CheckPrototypes(object, receiver, holder_obj, |
| + scratch1, scratch2, name, |
| + miss_label); |
| + |
| __ EnterInternalFrame(); |
| // Save the name_ register across the call. |
| __ push(name_); |
| @@ -584,9 +848,32 @@ class CallInterceptorCompiler BASE_EMBEDDED { |
| __ LeaveInternalFrame(); |
| } |
| - private: |
| + void LoadWithInterceptor(MacroAssembler* masm, |
| + Register receiver, |
| + Register holder, |
| + JSObject* holder_obj, |
| + Label* interceptor_succeeded) { |
| + __ EnterInternalFrame(); |
| + __ push(holder); // Save the holder. |
| + __ push(name_); // Save the name. |
| + |
| + CompileCallLoadPropertyWithInterceptor(masm, |
| + receiver, |
| + holder, |
| + name_, |
| + holder_obj); |
| + |
| + __ pop(name_); // Restore the name. |
| + __ pop(receiver); // Restore the holder. |
| + __ LeaveInternalFrame(); |
| + |
| + __ cmp(eax, Factory::no_interceptor_result_sentinel()); |
| + __ j(not_equal, interceptor_succeeded); |
| + } |
| + |
| + StubCompiler* stub_compiler_; |
| const ParameterCount& arguments_; |
| - int argc_; |
| + const int argc_; |
| Register name_; |
| }; |
| @@ -691,10 +978,12 @@ Register StubCompiler::CheckPrototypes(JSObject* object, |
| Register holder_reg, |
| Register scratch, |
| String* name, |
| + int push_at_depth, |
| Label* miss) { |
| // Check that the maps haven't changed. |
| Register result = |
| - masm()->CheckMaps(object, object_reg, holder, holder_reg, scratch, miss); |
| + masm()->CheckMaps(object, object_reg, holder, holder_reg, scratch, |
| + push_at_depth, miss); |
| // If we've skipped any global objects, it's not enough to verify |
| // that their maps haven't changed. |
| @@ -716,7 +1005,7 @@ Register StubCompiler::CheckPrototypes(JSObject* object, |
| object = JSObject::cast(object->GetPrototype()); |
| } |
| - // Return the register containin the holder. |
| + // Return the register containing the holder. |
| return result; |
| } |
| @@ -969,15 +1258,31 @@ Object* CallStubCompiler::CompileCallConstant(Object* object, |
| // unless we're doing a receiver map check. |
| ASSERT(!object->IsGlobalObject() || check == RECEIVER_MAP_CHECK); |
| + CallOptimization optimization(function); |
| + int depth = kInvalidProtoDepth; |
| + |
| switch (check) { |
| case RECEIVER_MAP_CHECK: |
| + __ IncrementCounter(&Counters::call_const, 1); |
| + |
| + if (optimization.is_simple_api_call() && !object->IsGlobalObject()) { |
| + depth = optimization.GetPrototypeDepthOfExpectedType( |
| + JSObject::cast(object), holder); |
| + } |
| + |
| + if (depth != kInvalidProtoDepth) { |
| + __ IncrementCounter(&Counters::call_const_fast_api, 1); |
| + ReserveSpaceForFastApiCall(masm(), eax); |
| + } |
| + |
| // Check that the maps haven't changed. |
| CheckPrototypes(JSObject::cast(object), edx, holder, |
| - ebx, eax, name, &miss); |
| + ebx, eax, name, depth, &miss); |
| // Patch the receiver on the stack with the global proxy if |
| // necessary. |
| if (object->IsGlobalObject()) { |
| + ASSERT(depth == kInvalidProtoDepth); |
| __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalReceiverOffset)); |
| __ mov(Operand(esp, (argc + 1) * kPointerSize), edx); |
| } |
| @@ -1062,19 +1367,26 @@ Object* CallStubCompiler::CompileCallConstant(Object* object, |
| UNREACHABLE(); |
| } |
| - // Get the function and setup the context. |
| - __ mov(edi, Immediate(Handle<JSFunction>(function))); |
| - __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
| + if (depth != kInvalidProtoDepth) { |
| + GenerateFastApiCall(masm(), optimization, argc); |
| + } else { |
| + // Get the function and setup the context. |
| + __ mov(edi, Immediate(Handle<JSFunction>(function))); |
| + __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
| - // Jump to the cached code (tail call). |
| - ASSERT(function->is_compiled()); |
| - Handle<Code> code(function->code()); |
| - ParameterCount expected(function->shared()->formal_parameter_count()); |
| - __ InvokeCode(code, expected, arguments(), |
| - RelocInfo::CODE_TARGET, JUMP_FUNCTION); |
| + // Jump to the cached code (tail call). |
| + ASSERT(function->is_compiled()); |
| + Handle<Code> code(function->code()); |
| + ParameterCount expected(function->shared()->formal_parameter_count()); |
| + __ InvokeCode(code, expected, arguments(), |
| + RelocInfo::CODE_TARGET, JUMP_FUNCTION); |
| + } |
| // Handle call cache miss. |
| __ bind(&miss); |
| + if (depth != kInvalidProtoDepth) { |
| + FreeSpaceForFastApiCall(masm(), eax); |
| + } |
| Handle<Code> ic = ComputeCallMiss(arguments().immediate()); |
| __ jmp(ic, RelocInfo::CODE_TARGET); |
| @@ -1108,18 +1420,16 @@ Object* CallStubCompiler::CompileCallInterceptor(Object* object, |
| // Get the receiver from the stack. |
| __ mov(edx, Operand(esp, (argc + 1) * kPointerSize)); |
| - CallInterceptorCompiler compiler(arguments(), ecx); |
| - CompileLoadInterceptor(&compiler, |
| - this, |
| - masm(), |
| - JSObject::cast(object), |
| - holder, |
| - name, |
| - &lookup, |
| - edx, |
| - ebx, |
| - edi, |
| - &miss); |
| + CallInterceptorCompiler compiler(this, arguments(), ecx); |
| + compiler.Compile(masm(), |
| + JSObject::cast(object), |
| + holder, |
| + name, |
| + &lookup, |
| + edx, |
| + ebx, |
| + edi, |
| + &miss); |
| // Restore receiver. |
| __ mov(edx, Operand(esp, (argc + 1) * kPointerSize)); |