v8的Frame

本文详细探讨了JavaScript引擎V8中的帧(frame)概念,解释了帧在代码运行和编译期的角色,以及如何通过帧链表构建调用堆栈。V8在函数调用时创建帧,分为caller和callee两个阶段。通过Execution::InstantiateFunction函数举例,展示了V8如何构建帧。文章深入分析了V8的调用序列,包括JSEntryStub和ArgumentsAdaptorTrampoline,阐述了从C调用JavaScript函数的整个过程,涉及EntryFrame和ArgumentsAdaptorFrame的创建与作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.何为frame
frame是一个代码运行期的概念,同时,在编译期又会用到它,它是一个active record,它记录了函数当前运行的信息。我们使用IDE进行调试的时候,用到的call stack,就是通过frame链表得到的。简单来说,每次的函数调用就会在栈上形成一个frame,同时该frame会连接到frame链表中成为当前的frame。一般来说,cpu会有一个专门的寄存器来保存当前frame的地址,在intel cpu中使用ebp。
frame可能包括的内容有:
1.parameters
2.return address
3.previous frame address
4.local variables
需要说明的是,frame并不是一次就创建好的,它的创建分为两部分
1.caller在函数调用前,要把参数分别入栈,然后把调用完成后的返回地址入栈,然后跳转到函数首地址
2.callee首先把frame pointer入栈,同时把sp赋值给fp,这样就把当前frame加入到frame链表中,然后再构建当前函数需要加入到frame中的内容
在函数中只需要通过fp及其偏移量就可以访问frame中的内容。
frame的格式和内容是与CPU,编译器、编译语言相关的,他们是一种convention,例如C/C++就有自己的ABI,但是不同的编译器,不同的CPU的ABI又不完全相同,也就是说,我们完全可以定义自己的frame,只要编译器,调试器能够识别就行,v8就是这么做的

二.v8的frame
我们以execution.cc中的Execution::InstantiateFunction为例,说明v8是如何构建frame的。

Handle<JSFunction> Execution::InstantiateFunction(
    Handle<FunctionTemplateInfo> data,
    bool* exc) {
  Isolate* isolate = data->GetIsolate();
  // Fast case: see if the function has already been instantiated
  int serial_number = Smi::cast(data->serial_number())->value();
  Object* elm =
      isolate->native_context()->function_cache()->
          GetElementNoExceptionThrown(serial_number);
  if (elm->IsJSFunction()) return Handle<JSFunction>(JSFunction::cast(elm));
  // The function has not yet been instantiated in this context; do it.
  Handle<Object> args[] = { data };
  Handle<Object> result = Call(isolate->instantiate_fun(),
                               isolate->js_builtins_object(),
                               ARRAY_SIZE(args),
                               args,
                               exc);
  if (*exc) return Handle<JSFunction>::null();
  return Handle<JSFunction>::cast(result);
}
其中重点是Call函数,如下:
Handle<Object> Execution::Call(Handle<Object> callable,
                               Handle<Object> receiver,
                               int argc,
                               Handle<Object> argv[],
                               bool* pending_exception,
                               bool convert_receiver) {
  *pending_exception = false;

  if (!callable->IsJSFunction()) {
    callable = TryGetFunctionDelegate(callable, pending_exception);
    if (*pending_exception) return callable;
  }
  Handle<JSFunction> func = Handle<JSFunction>::cast(callable);

  。。。。

  return Invoke(false, func, receiver, argc, argv, pending_exception);
}
Invoke
重点是Invoke函数,如下:
static Handle<Object> Invoke(bool is_construct,
                             Handle<JSFunction> function,
                             Handle<Object> receiver,
                             int argc,
                             Handle<Object> args[],
                             bool* has_pending_exception) {
  Isolate* isolate = function->GetIsolate();

  // Entering JavaScript.
  VMState state(isolate, JS);

  // Placeholder for return value.
  MaybeObject* value = reinterpret_cast<Object*>(kZapValue);

  typedef Object* (*JSEntryFunction)(byte* entry,
                                     Object* function,
                                     Object* receiver,
                                     int argc,
                                     Object*** args);

  <1>isolate->factory()->js_entry_code()和isolate->factory()->js_construct_entry_code()
  Handle<Code> code = is_construct
      ? isolate->factory()->js_construct_entry_code()
      : isolate->factory()->js_entry_code();

  // Convert calls on global objects to be calls on the global
  // receiver instead to avoid having a 'this' pointer which refers
  // directly to a global object.
  if (receiver->IsGlobalObject()) {
    Handle<GlobalObject> global = Handle<GlobalObject>::cast(receiver);
    receiver = Handle<JSObject>(global->global_receiver());
  }

  // Make sure that the global object of the context we're about to
  // make the current one is indeed a global object.
  ASSERT(function->context()->global_object()->IsGlobalObject());

  {
    // Save and restore context around invocation and block the
    // allocation of handles without explicit handle scopes.
    SaveContext save(isolate);
    NoHandleAllocation na(isolate);
    JSEntryFunction stub_entry = FUNCTION_CAST<JSEntryFunction>(code->entry());

    // Call the function through the right JS entry stub.
    byte* function_entry = function->code()->entry();
    JSFunction* func = *function;
    Object* recv = *receiver;
    Object*** argv = reinterpret_cast<Object***>(args);
    value =
        <2>#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
  (entry(p0, p1, p2, p3, p4))

        CALL_GENERATED_CODE(stub_entry, function_entry, func, recv, argc, argv);
  }

#ifdef VERIFY_HEAP
  value->Verify();
#endif

  // Update the pending exception flag and return the value.
  *has_pending_exception = value->IsException();
  ASSERT(*has_pending_exception == isolate->has_pending_exception());
  if (*has_pending_exception) {
    isolate->ReportPendingMessages();
    if (isolate->pending_exception()->IsOutOfMemory()) {
      if (!isolate->ignore_out_of_memory()) {
        V8::FatalProcessOutOfMemory("JS", true);
      }
    }
#ifdef ENABLE_DEBUGGER_SUPPORT
    // Reset stepping state when script exits with uncaught exception.
    if (isolate->debugger()->IsDebuggerActive()) {
      isolate->debug()->ClearStepping();
    }
#endif  // ENABLE_DEBUGGER_SUPPORT
    return Handle<Object>();
  } else {
    isolate->clear_pending_message();
  }

  return Handle<Object>(value->ToObjectUnchecked(), isolate);
}
js_entry_code()
下面我们分析上述的调用序列
<1>isolate->factory()->js_entry_code()和isolate->factory()->js_construct_entry_code()
factory->js_entry_code()函数是在factory.h中通过如下的宏定义的
#define ROOT_ACCESSOR(type, name, camel_name)                                  \
  inline Handle<type> name() {                                                 \
    return Handle<type>(BitCast<type**>(                                       \
        &isolate()->heap()->roots_[Heap::k##camel_name##RootIndex]));          \
  }
  ROOT_LIST(ROOT_ACCESSOR)
ROOT_LIST是在heap.h中定义的,其中
V(Code, js_entry_code, JsEntryCode)                                          \
V(Code, js_construct_entry_code, JsConstructEntryCode)                       \
也就是说,该函数返回的是heap中的roots_数组中的Code对象,参看 Heap RootObject的初始化,我们可以知道js_entry_code对象是在 Heap :: CreateJSEntryStub ()函数中创建的
void Heap :: CreateJSEntryStub () {
  JSEntryStub stub ;
  set_js_entry_code (* stub . GetCode ( isolate ()));
}
JSEntryStub是CodeStub类的派生类,CodeStub定义了GetCode的流程,JSEntryStub实现GenerateBody虚函数,所以重点在于JSEntryStub::GenerateBody函数的实现
void JSEntryStub :: GenerateBody ( MacroAssembler * masm , bool is_construct ) {
  Label invoke , handler_entry , exit ;
  Label not_outermost_js , not_outermost_js_2 ;

  // Set up frame.
  //这里构建frame的后半部分
  __ push ( ebp );
  __ mov ( ebp , esp );

  // Push marker in two places.
  int marker = is_construct ? StackFrame :: ENTRY_CONSTRUCT : StackFrame :: ENTRY ;
  //marker用来表示这是一个frame的类型,同时它又起到占位符的作用,可以作为context和function的slot
  __ push ( Immediate ( Smi :: FromInt ( marker )));  // context slot
  __ push ( Immediate ( Smi :: FromInt ( marker )));  // function slot
  // Save callee-saved registers (C calling conventions).
  __ push ( edi );
  __ push ( esi );
  __ push ( ebx );

  // Save copies of the top frame descriptor on the stack.
  <1>ExternalReference c_entry_fp
  ExternalReference c_entry_fp ( Isolate :: kCEntryFPAddress , masm -> isolate ());
  __ push ( Operand :: StaticVariable ( c_entry_fp ));

  // If this is the outermost JS call, set js_entry_sp value.
  ExternalReference js_entry_sp ( Isolate :: kJSEntrySPAddress ,
                                masm -> isolate ());
  //判断js_entry_sp的内容是否为null
  __ cmp ( Operand :: StaticVariable ( js_entry_sp ), Immediate (0));
  //如果不是null,跳转到not_outmost_js
  __ j ( not_equal , & not_outermost_js , Label :: kNear );
  //如果是null,说明是outmost js entry
  //把ebp赋值给js_entry_sp
  __ mov ( Operand :: StaticVariable ( js_entry_sp ), ebp );
  //push一个marker表示这是一个outmost_jsentry_frame
  __ push ( Immediate ( Smi :: FromInt ( StackFrame :: OUTERMOST_JSENTRY_FRAME )));
  //跳转到invoke
  __ jmp (& invoke , Label :: kNear );
  __ bind (& not_outermost_js );
  //not _outmost标签
  //push一个marker表示这是一个inner_jsentry_frame
  __ push ( Immediate ( Smi :: FromInt ( StackFrame :: INNER_JSENTRY_FRAME )));

  // Jump to a faked try block that does the invoke, with a faked catch
  // block that sets the pending exception.
  //跳转到invoke标签
  __ jmp (& invoke );
  __ bind (& handler_entry );
  handler_offset_ = handler_entry . pos ();
  // Caught exception: Store result (exception) in the pending exception
  // field in the JSEnv and return a failure sentinel.
  ExternalReference pending_exception ( Isolate :: kPendingExceptionAddress ,
                                      masm -> isolate ());
  __ mov ( Operand :: StaticVariable ( pending_exception ), eax );
  __ mov ( eax , reinterpret_cast < int32_t >( Failure :: Exception ()));
  __ jmp (& exit );

  // Invoke: Link this frame into the handler chain.  There's only one
  // handler block in this code object, so its index is 0.
  __ bind (& invoke );
  //这里应该是用来异常处理的,还需要进一步分析
  __ PushTryHandler ( StackHandler :: JS_ENTRY , 0);

  // Clear any pending exceptions.
  __ mov ( edx , Immediate ( masm -> isolate ()-> factory ()-> the_hole_value ()));
  __ mov ( Operand :: StaticVariable ( pending_exception ), edx );

  // Fake a receiver (NULL).
  __ push ( Immediate (0));  // receiver

  // Invoke the function by calling through JS entry trampoline builtin and
  // pop the faked function when we return. Notice that we cannot store a
  // reference to the trampoline code directly in this stub, because the
  // builtin stubs may not have been generated yet.
  if ( is_construct ) {
    ExternalReference construct_entry ( Builtins :: kJSConstructEntryTrampoline ,
                                      masm -> isolate ());
    __ mov ( edx , Immediate ( construct_entry ));
  } else {
   <2>ExternalReference entry ( Builtins:: kJSEntryTrampoline
    ExternalReference entry ( Builtins :: kJSEntryTrampoline ,
                            masm -> isolate ());
    //mov指令执行后,edx中存储的是Code**
    __ mov ( edx , Immediate ( entry ));
  }
  //Operand(edx , 0)对edx进行接引用,mov指令执行后,edx存储的是Code*
  __ mov ( edx , Operand ( edx , 0));  // deref address
  //lea指令是把源操作数的有效地址,即偏移量存储在指定的寄存器中,这里edx+Code::kHeaderSize就是有效地址,它被存储在edx中
  __ lea ( edx , FieldOperand ( edx , Code :: kHeaderSize ));
  __ call ( edx );

  // Unlink this frame from the handler chain.
  __ PopTryHandler ();

  __ bind (& exit );
  // Check if the current stack frame is marked as the outermost JS frame.
  __ pop ( ebx );
  __ cmp ( ebx , Immediate ( Smi :: FromInt ( StackFrame :: OUTERMOST_JSENTRY_FRAME )));
  __ j ( not_equal , & not_outermost_js_2 );
  __ mov ( Operand :: StaticVariable ( js_entry_sp ), Immediate (0));
  __ bind (& not_outermost_js_2 );

  // Restore the top frame descriptor from the stack.
  __ pop ( Operand :: StaticVariable ( ExternalReference (
      Isolate :: kCEntryFPAddress ,
      masm -> isolate ())));

  // Restore callee-saved registers (C calling conventions).
  __ pop ( ebx );
  __ pop ( esi );
  __ pop ( edi );
  __ add ( esp , Immediate (2 * kPointerSize ));  // remove markers

  // Restore frame pointer and return.
  __ pop ( ebp );
  __ ret (0);
}
下面分别对标号1和2进行说明
<1>ExternalReference c_entry_fp Isolate:: kCEntryFPAddress
关于ExternalReference类的注释如下:
// An ExternalReference represents a C++ address used in the generated
// code. All references to C++ functions and variables must be encapsulated in
// an ExternalReference instance. This is done in order to track the origin of
// all external references in the code so that they can be bound to the correct
// addresses when deserializing a heap.
通俗来说,在构建该类的时候,传入不同的类型,可以得到一个地址,这个转换过程是被ExternalReference封装的,对于Isolate::kCEntryFPAddress而言,它对应的地址是Isolate:: isolate_addresses_[]数组中一个元素,该数组是在Isolate::init函数中初始化的
#define ASSIGN_ELEMENT ( CamelName , hacker_name )                  \
  isolate_addresses_ [ Isolate :: k # # CamelName # # Address ] =          \
      reinterpret_cast < Address >( hacker_name # # _address ());
  FOR_EACH_ISOLATE_ADDRESS_NAME ( ASSIGN_ELEMENT )
其中 FOR_EACH_ISOLATE_ADDRESS_NAME 的定义如下:
#define FOR_EACH_ISOLATE_ADDRESS_NAME ( C )                \
  C ( Handler , handler )                                   \
  C ( CEntryFP , c_entry_fp )                               \
  C ( Context , context )                                   \
  C ( PendingException , pending_exception )                \
  C ( ExternalCaughtException , external_caught_exception ) \
  C ( JSEntrySP , js_entry_sp )
对于CEntryFP,对应的调用isolate::c_entry_fp_address()函数,即
inline Address * c_entry_fp_address () {
    return & thread_local_top_ . c_entry_fp_ ;
  }
其中 thread_local_top_  .  c_entry_fp_是一个Address类型的变量。
通过上面的分析 ExternalReference   c_entry_fp  (  Isolate ::  kCEntryFPAddress  ,  masm  ->  isolate ());  c_entry_fp中存储的是isolate::thread_local_top_::c_entry_fp_的地址,在isolate初始化的时候,c_entry_fp_的内容为NULL
__   push  (  Operand ::  StaticVariable  ( c_entry_fp  ));该指令,取c_entry_fp的内容,然后入栈,也就是NULL入栈
同样的在后面使用到了 ExternalReference  jsentry_sp,它存储的是isolate::thread_local_top_::js_entry_fp_的地址,不同的是后面的代码对其进行了赋值

JSEntryTrampoline
<2>ExternalReference entry Builtins:: kJSEntryTrampoline
这个 ExternalReference获取的是isolate::builtins::buitins_[]中的Code对象指针的地址,参看 builtins的初始化,我们可以知道JSEntryTrampoline是通过 BUILTIN_LIST_A定义的,它对应的Code对象是通过 Generate_JSEntryTrampoline函数生成的,该函数是在builtins_ia32.cc中定义的,在调用Code对象的函数之前的栈状况如下:
生成该Code对象的函数如下:
static void Generate_JSEntryTrampolineHelper ( MacroAssembler * masm ,
                                             bool is_construct ) {
  // Clear the context before we push it when entering the internal frame.
  __ Set ( esi , Immediate (0));

  {
   <1>FrameScope
    FrameScope scope ( masm , StackFrame :: INTERNAL );

    // Load the previous frame pointer (ebx) to access C arguments
    //载入上一个frame的地址
    __ mov ( ebx , Operand ( ebp , 0));

    // Get the function from the frame and setup the context.
    <2>这里非常奇怪
    __ mov ( ecx , Operand ( ebx , EntryFrameConstants :: kFunctionArgOffset ));
    __ mov ( esi , FieldOperand ( ecx , JSFunction :: kContextOffset ));

    // Push the function and the receiver onto the stack.
    __ push ( ecx );
    __ push ( Operand ( ebx , EntryFrameConstants :: kReceiverArgOffset ));

    // Load the number of arguments and setup pointer to the arguments.
    __ mov ( eax , Operand ( ebx , EntryFrameConstants :: kArgcOffset ));
    __ mov ( ebx , Operand ( ebx , EntryFrameConstants :: kArgvOffset ));

    // Copy arguments to the stack in a loop.
    Label loop , entry ;
    __ Set ( ecx , Immediate (0));
    __ jmp (& entry );
    __ bind (& loop );
    __ mov ( edx , Operand ( ebx , ecx , times_4 , 0));  // push parameter from argv
    __ push ( Operand ( edx , 0));  // dereference handle
    __ inc ( ecx );
    __ bind (& entry );
    __ cmp ( ecx , eax );
    __ j ( not_equal , & loop );

    // Get the function from the stack and call it.
    // kPointerSize for the receiver.
    <3>
    __ mov ( edi , Operand ( esp , eax , times_4 , kPointerSize ));

    // Invoke the code.
    if ( is_construct ) {
      CallConstructStub stub ( NO_CALL_FUNCTION_FLAGS );
      __ CallStub (& stub );
    } else {
      ParameterCount actual ( eax );
     <4>
      __ InvokeFunction ( edi , actual , CALL_FUNCTION ,
                        NullCallWrapper (), CALL_AS_METHOD );
    }

    // Exit the internal frame. Notice that this also removes the empty.
    // context and the function left on the stack by the code
    // invocation.
  }
  __ ret ( kPointerSize );  // Remove receiver.
}
<1>FrameScope
在类的构造函数会调用MacroAssembler::EnterFrame构建frame,
void MacroAssembler::EnterFrame(StackFrame::Type type) {
  push(ebp);
  mov(ebp, esp);
  push(esi);
  //push一个marker入栈
  push(Immediate(Smi::FromInt(type)));
  //push一个undefined object入栈
  push(Immediate(CodeObject()));
  if (emit_debug_code()) {
    cmp(Operand(esp, 0), Immediate(isolate()->factory()->undefined_value()));
    Check(not_equal, "code object not properly patched");
  }
}
<2> EntryFrameConstants ::  kFunctionArgOffset
__   mov   (  ecx Operand  ( ebx   EntryFrameConstants ::   kFunctionArgOffset  ));此时ebx为上一个frame的地址, EntryFrameConstants ::   kFunctionArgOffset=+3 * kPointerSize,那么
Operand  ( ebx   EntryFrameConstants ::   kFunctionArgOffset  )就表示取上一个frame  +3 * kPointerSize的内容,根据我们上面的分析, JSEntryStub   :: GenerateBody在函数伊始只是构建frame的后半部分,那么这里的所引用到的前部分在哪里呢?
    回归到Invoke函数,它调用了 CALL_GENERATED_CODE(stub_entry, function_entry, func, recv, argc, argv);
其中宏 CALL_GENERATED_CODE的定义如下:
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
  (entry(p0, p1, p2, p3, p4))
stub_entry是一个函数指针,它的值就是js_entry_code()的entry,也就是 JSEntryStub   :: GenerateBody生成的那段代码,也就说Invoke函数最后会调用这段代码,既然是函数调用,就必然存在参数入栈,返回地址入栈,然后跳转到指定地址的过程,这样就与 EntryFrameConstants ::   kFunctionArgOffset 的分析联系到一起,按照p4到p0的顺序依次入栈,最后返回地址入栈,然后进入到js_entry_code,ebp入栈。这样我们的栈如下所示:

由此, EntryFrameConstants ::  kFunctionArgOffset应该对应的是func参数。 __   mov  (  esi FieldOperand  ( ecx  JSFunction ::  kContextOffset  ));
印证了我们的分析,func参数是一个JSFunction对象,这里取它的context字段,赋值给esi
<3>获取func对象指针,并赋值给edi
<4>此时的栈情况如下


InvokeFunction的函数实现如下:
void MacroAssembler ::InvokeFunction( Register fun ,
                                    const ParameterCount & actual,
                                    InvokeFlag flag ,
                                    const CallWrapper & call_wrapper,
                                    CallKind call_kind ) {
  // You can't call a function without a valid frame.
  ASSERT(flag == JUMP_FUNCTION || has_frame());

  ASSERT(fun .is( edi));
  //edi存储的是JSFunction对象指针,FieldOperand( edi JSFunction :: kSharedFunctionInfoOffset )得到SharedFunctionInfo对象指针
  mov(edx , FieldOperand( edi, JSFunction ::kSharedFunctionInfoOffset));
  //FieldOperand( edi JSFunction :: kContextOffset))得到的是Context对象指针
  mov(esi , FieldOperand( edi, JSFunction ::kContextOffset));
  //FieldOperand( edx SharedFunctionInfo :: kFormalParameterCountOffset )得到的是SharedFunctionInfo的FormalParameterCount字段的内容
  mov(ebx , FieldOperand( edx, SharedFunctionInfo ::kFormalParameterCountOffset));
  SmiUntag(ebx );
  
  //现在ebx表示FormalParameterCount也就是函数声明的参数个数
  ParameterCount expected (ebx);

  //edi是JSFunction,FieldOperand ( ediJSFunction ::kCodeEntryOffset )得到其CodeEntry
  InvokeCode(FieldOperand (edi, JSFunction::kCodeEntryOffset ),
             expected, actual , flag, call_wrapper, call_kind );
}

void MacroAssembler ::InvokeCode( const Operand & code,
                                const ParameterCount & expected,
                                const ParameterCount & actual,
                                InvokeFlag flag ,
                                const CallWrapper & call_wrapper,
                                CallKind call_kind ) {
  // You can't call a function without a valid frame.
  ASSERT(flag == JUMP_FUNCTION || has_frame());

  Label done ;
  bool definitely_mismatches = false;
  //这里还需要对参数对最后的整理,函数定义如下所示
  InvokePrologue(expected , actual, Handle<Code >::null(), code,
                 & done, &definitely_mismatches , flag, Label::kNear ,
                 call_wrapper, call_kind );
  if (!definitely_mismatches ) {
    if (flag == CALL_FUNCTION) {
      call_wrapper.BeforeCall (CallSize( code));
      SetCallKind(ecx , call_kind);
      //这里最终会调用Invoke函数中指定的function
      call(code );
      call_wrapper.AfterCall ();
    } else {
      ASSERT(flag == JUMP_FUNCTION);
      SetCallKind(ecx , call_kind);
      jmp(code );
    }
    bind(&done );
  }
}
void MacroAssembler ::InvokePrologue( const ParameterCount & expected,
                                    const ParameterCount & actual,
                                    Handle<Code > code_constant,
                                    const Operand & code_operand,
                                    Label* done ,
                                    bool* definitely_mismatches ,
                                    InvokeFlag flag ,
                                    Label::Distance done_near,
                                    const CallWrapper & call_wrapper,
                                    CallKind call_kind ) {
  bool definitely_matches = false;
  * definitely_mismatches = false ;
  Label invoke ;
  if (expected .is_immediate()) {
    ASSERT(actual .is_immediate());
    if (expected .immediate() == actual.immediate ()) {
      definitely_matches = true ;
    } else {
      mov(eax , actual. immediate());
      const int sentinel = SharedFunctionInfo::kDontAdaptArgumentsSentinel ;
      if (expected .immediate() == sentinel) {
        // Don't worry about adapting arguments for builtins that
        // don't want that done. Skip adaption code by making it look
        // like we have a match between expected and actual number of
        // arguments.
        definitely_matches = true ;
      } else {
        * definitely_mismatches = true ;
        mov(ebx , expected. immediate());
      }
    }
  } else {
    if (actual .is_immediate()) {
      // Expected is in register, actual is immediate. This is the
      // case when we invoke function values without going through the
      // IC mechanism.
      cmp(expected .reg(), actual.immediate ());
      j(equal , &invoke);
      ASSERT(expected .reg(). is(ebx ));
      mov(eax , actual. immediate());
    } else if (!expected. reg().is (actual. reg())) {
      // Both expected and actual are in (different) registers. This
      // is the case when we invoke functions using call and apply.
      cmp(expected .reg(), actual.reg ());
      j(equal , &invoke);
      ASSERT(actual .reg(). is(eax ));
      ASSERT(expected .reg(). is(ebx ));
    }
  }
  //以上这一段显然是比较expected参数个数与实际参数个数是否一致,如果一致的话,直接跳转到invoke

  if (!definitely_matches ) {
    //这一句与上面的JSEntryTrampoline是一样的,也是从builtins中获取一个Code对象
    Handle<Code > adaptor =
        isolate()->builtins ()->ArgumentsAdaptorTrampoline();
    if (!code_constant .is_null()) {
      mov(edx , Immediate( code_constant));
      add(edx , Immediate( Code::kHeaderSize - kHeapObjectTag));
    } else if (!code_operand. is_reg(edx )) {
      //把code entry设置给edx
      mov(edx , code_operand);
    }

    if (flag == CALL_FUNCTION) {
      call_wrapper.BeforeCall (CallSize( adaptor, RelocInfo ::CODE_TARGET));
      //把call_kind设置给ecx
      SetCallKind(ecx , call_kind);
      //显然这里调用的就是上面获取的Code对象,我们还需要分析生成这个Code对象的代码
      call(adaptor , RelocInfo:: CODE_TARGET);
      call_wrapper.AfterCall ();
      if (!*definitely_mismatches ) {
        jmp(done , done_near);
      }
    } else {
      SetCallKind(ecx , call_kind);
      jmp(adaptor , RelocInfo:: CODE_TARGET);
    }
    bind(&invoke );
  }
}

ArgumentsAdaptorTrampoline
还是按照老方法,在buitins.h中查找 ArgumentsAdaptorTrampoline,发现它在BUILTIN_LIST_A宏中定义,说明它定义在bultins-ia32.cc中定义,在该文件中找到如下函数
void Builtins :: Generate_ArgumentsAdaptorTrampoline ( MacroAssembler * masm ) {
  // ----------- S t a t e -------------
  //  -- eax : actual number of arguments
  //  -- ebx : expected number of arguments
  //  -- ecx : call kind information
  //  -- edx : code entry to call
  // -----------------------------------

  Label invoke , dont_adapt_arguments ;
  __ IncrementCounter ( masm -> isolate ()-> counters ()-> arguments_adaptors (), 1);

  Label enough , too_few ;
  //比较真实参数个数与期望参数个数
  __ cmp ( eax , ebx );
  //如果真实参数个数比期望参数个数小,跳转到too_few
  __ j ( less , & too_few );
  //此时属于真实参数个数大于期望参数个数的情况,还需要比较期望参数个数,与SharedFunctionInfo ::kDontAdaptArgumentsSentinel(-1)
  __ cmp ( ebx , SharedFunctionInfo :: kDontAdaptArgumentsSentinel );
  //如果相等,则跳转到dont_adapt_arguments,不再进行参数适配
  __ j ( equal , & dont_adapt_arguments );

  {  // Enough parameters: Actual >= expected.
    //此时Actual >= expected
    __ bind (& enough );
    //进入ArmentsAdaptorFrame,该函数如下所示
    EnterArgumentsAdaptorFrame ( masm );

    // Copy receiver and all expected arguments.
    // actual  >= expected的情况,把receiver和期望的参数入栈,这里是从receiver开始的,然后是arg[0],argv[1],...,可参看下图ArgumentAdaptorFrame部分,也就是说多余的参数将会被忽略
    const int offset = StandardFrameConstants :: kCallerSPOffset ;
    __ lea ( eax , Operand ( ebp , eax , times_4 , offset ));
    __ mov ( edi , -1);  // account for receiver

    Label copy ;
    __ bind (& copy );
    __ inc ( edi );
    __ push ( Operand ( eax , 0));
    __ sub ( eax , Immediate ( kPointerSize ));
    __ cmp ( edi , ebx );
    __ j ( less , & copy );
    __ jmp (& invoke );
  }

  {  // Too few parameters: Actual < expected.
   // actual < expected这种情况,则是把所有的receiver和argument入栈之后,在push足够数目的undefined对象
    __ bind (& too_few );
    EnterArgumentsAdaptorFrame ( masm );

    // Copy receiver and all actual arguments.
    const int offset = StandardFrameConstants :: kCallerSPOffset ;
    __ lea ( edi , Operand ( ebp , eax , times_4 , offset ));
    // ebx = expected - actual.
    __ sub ( ebx , eax );
    // eax = -actual - 1
    __ neg ( eax );
    __ sub ( eax , Immediate (1));

    Label copy ;
    __ bind (& copy );
    __ inc ( eax );
    __ push ( Operand ( edi , 0));
    __ sub ( edi , Immediate ( kPointerSize ));
    __ test ( eax , eax );
    __ j ( not_zero , & copy );

    // Fill remaining expected arguments with undefined values.
    Label fill ;
    __ bind (& fill );
    __ inc ( eax );
    __ push ( Immediate ( masm -> isolate ()-> factory ()-> undefined_value ()));
    __ cmp ( eax , ebx );
    __ j ( less , & fill );
  }

  // Call the entry point.
  __ bind (& invoke );
  // Restore function pointer.
  __ mov ( edi , Operand ( ebp , JavaScriptFrameConstants :: kFunctionOffset ));
  //调用edx指定的代码,在MacroAssembler :: InvokeFunction中,已经指定了esi,edi的值,edi指向JSFuntion,esi指向Context
  __ call ( edx );

  // Store offset of return address for deoptimizer.
  masm -> isolate ()-> heap ()-> SetArgumentsAdaptorDeoptPCOffset ( masm -> pc_offset ());

  // Leave frame and return.
  LeaveArgumentsAdaptorFrame ( masm );
  __ ret (0);

  // -------------------------------------------
  // Dont adapt arguments.
  // -------------------------------------------
  __ bind (& dont_adapt_arguments );
  __ jmp ( edx );
}

static void EnterArgumentsAdaptorFrame ( MacroAssembler * masm ) {
  __ push ( ebp );
  __ mov ( ebp , esp );

  // Store the arguments adaptor context sentinel.
  __ push ( Immediate ( Smi :: FromInt ( StackFrame :: ARGUMENTS_ADAPTOR )));

  // Push the function on the stack.
  __ push ( edi );

  // Preserve the number of arguments on the stack. Must preserve eax,
  // ebx and ecx because these registers are used when copying the
  // arguments and the receiver.
  STATIC_ASSERT ( kSmiTagSize == 1);
  __ lea ( edi , Operand ( eax , eax , times_1 , kSmiTag ));
  __ push ( edi );
}
调用该函数后,栈的情况如下:

full-codegen-ia32.cc中FullCodeGenerator::Generate()用于生成javascript的code,其注释如下:
// Generate code for a JS function.  On entry to the function the receiver
// and arguments have been pushed on the stack left to right, with the
// return address on top of them.  The actual argument count matches the
// formal parameter count expected by the function.

//
// The live registers are:
//   o edi: the JS function object being called (i.e. ourselves)
//   o esi: our context

//   o ebp: our caller's frame pointer
//   o esp: stack pointer (pointing to return address)
//
// The function builds a JS frame.  Please see JavaScriptFrameConstants in
// frames-ia32.h for its layout.
这里清楚的说明,此时参数是与实际函数声明匹配的,这就对应了我们通过ArgumentAdaptorFrame所做的事情,同时edi和esi也指向了各自所需要的值。
至此,我们就清楚了从C函数调用javascript函数的整个过程。上图的栈情况已经很清楚的说明了函数调用的流程,让我们再回顾一下要点
->Invoke
->js_entry_code()
这段代码是存储在Code对象中的,该对象指针是存储在heap的root数组中的,同时它又是放在Stub中cache的,它是通过JSEntryStub创建的,这段代码创建了EntryFrame
->JSEntryTrampoline
这段代码是存储在Code对象中的,该对象指针存储在isolate::builtin::builtins[]数组中,它是在builtin初始化的时候,通过Generate函数生成的,这段代码重新整理了C的参数,为参数适配做准备
->ArgumentAdaptor
这段代码是存储在Code对象中的,该对象指针存储在isolate::builtin::builtins[]数组中,它是在builtin初始化的时候,通过Generate函数生成的,这段代码是为了使得实际参数与期望参数匹配
在上面的调用过程中,使用到的Frame有:
EntryFrame
在frame-ia32.h中有如下的定义
class EntryFrameConstants : public AllStatic {
public:
  static const int kCallerFPOffset      = -6 * kPointerSize;

  static const int kFunctionArgOffset   = +3 * kPointerSize;
  static const int kReceiverArgOffset   = +4 * kPointerSize;
  static const int kArgcOffset          = +5 * kPointerSize;
  static const int kArgvOffset          = +6 * kPointerSize;
};
我们可以看到这里的offset与上图中绿色部分是吻合的

ArgumentAdaptorFrame
在frame-ia32.h中有如下的定义
class ArgumentsAdaptorFrameConstants : public AllStatic {
public:
  // FP-relative.
  static const int kLengthOffset = StandardFrameConstants::kExpressionsOffset;

  static const int kFrameSize =
      StandardFrameConstants::kFixedFrameSize + kPointerSize;
};
我们可以看到这里的offset与上图中土色部分是吻合的

CEntry
Heap::CreateFixStub
-> CEntryStub :: GenerateAheadOfTime
该函数会生成一个Stub存入factory的stub dict中






评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值