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);
//把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 (¬_outermost_js);
//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 (¬_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)
<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.
}
__ 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 ( edi, JSFunction ::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
)) {
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.
__
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
(©);
__
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
(©);
__
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
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中