• 主页
所有文章 友链 关于我

  • 主页

v8 - inline cache

2022-07-29

初探 inline cache

前言

好记性不如烂笔头,为了能了解一下v8里inline cache的具体实现,特此记录一下调试的过程

环境

Ubuntu20.04 LTS

v8 version 9.8.177.9 x64

测试代码:

1
2
3
4
5
6
7
8
9
10
11
function foo(a){
return a.x;
}
%DebugPrint(foo);
%SystemBreak();
var c = {x:1.1};
for(var i = 0;i<10;i++){
foo(c);
%DebugPrint(foo);
%SystemBreak();
}

第1次调用

CompileLazy

code指向CompileLazy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DebugPrint: 0x3226082935dd: [Function] in OldSpace
- map: 0x3226082c2281 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x322608283b79 <JSFunction (sfi = 0x32260820abed)>
- elements: 0x322608002231 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype:
- initial_map:
- shared_info: 0x322608293495 <SharedFunctionInfo foo>
- name: 0x3226082933c5 <String[3]: #foo>
- builtin: CompileLazy
- formal_parameter_count: 1
- kind: NormalFunction
- context: 0x322608283649 <NativeContext[263]>
- code: 0x322600005201 <Code BUILTIN CompileLazy>
- source code: (a){
return a.x;
}

CompileLazy :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/builtins/builtins-lazy-gen.cc
TF_BUILTIN(CompileLazy, LazyBuiltinsAssembler) {
auto function = Parameter<JSFunction>(Descriptor::kTarget);

CompileLazy(function);
}

void LazyBuiltinsAssembler::CompileLazy(TNode<JSFunction> function) {
// First lookup code, maybe we don't need to compile!
Label compile_function(this, Label::kDeferred);
// ... //

BIND(&compile_function);
GenerateTailCallToReturnedCode(Runtime::kCompileLazy, function);
}

runtime complielazy

会执行runtime的complielazy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/runtime/runtime-compiler.cc
RUNTIME_FUNCTION(Runtime_CompileLazy) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);

Handle<SharedFunctionInfo> sfi(function->shared(), isolate);

#ifdef DEBUG
if (FLAG_trace_lazy && !sfi->is_compiled()) {
PrintF("[unoptimized: ");
function->PrintName();
PrintF("]\n");
}
#endif

StackLimitCheck check(isolate);
if (check.JsHasOverflowed(kStackSpaceRequiredForCompilation * KB)) {
return isolate->StackOverflow();
}
IsCompiledScope is_compiled_scope;
if (!Compiler::Compile(isolate, function, Compiler::KEEP_EXCEPTION,
&is_compiled_scope)) {
return ReadOnlyRoots(isolate).exception();
}
DCHECK(function->is_compiled());
// TODO(v8:11880): avoid roundtrips between cdc and code.
return ToCodeT(function->code());
}

Compiler::Compile

编译任务主要在Compiler::Compile里完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/codegen/compiler.cc
// static
bool Compiler::Compile(Isolate* isolate, Handle<SharedFunctionInfo> shared_info,
ClearExceptionFlag flag,
IsCompiledScope* is_compiled_scope,
CreateSourcePositions create_source_positions_flag) {
// ... //
if (!IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs(
isolate, shared_info, script, &parse_info, isolate->allocator(),
is_compiled_scope, &finalize_unoptimized_compilation_data_list,
nullptr)) {
return FailWithPendingException(isolate, script, &parse_info, flag);
}

FinalizeUnoptimizedCompilation(isolate, script, flags, &compile_state,
finalize_unoptimized_compilation_data_list);

// ... //
return true;
}

IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs 函数会调用 ExecuteSingleUnoptimizedCompilationJob和FinalizeSingleUnoptimizedCompilationJob来生成jsFunction的code,这两个函数最后会执行到 InterpreterCompilationJob::Status InterpreterCompilationJob::ExecuteJobImpl和InterpreterCompilationJob::Status InterpreterCompilationJob::FinalizeJobImpl,其实现都在src/interpreter/interpreter.cc里

FinalizeSingleUnoptimizedCompilationJob里调用了InstallUnoptimizedCode来给JSFunction安装feedback_metadata,code和BytecodeArray:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// src/codegen/compiler.cc
template <typename IsolateT>
CompilationJob::Status FinalizeSingleUnoptimizedCompilationJob(
UnoptimizedCompilationJob* job, Handle<SharedFunctionInfo> shared_info,
IsolateT* isolate,
FinalizeUnoptimizedCompilationDataList*
finalize_unoptimized_compilation_data_list) {
UnoptimizedCompilationInfo* compilation_info = job->compilation_info();

CompilationJob::Status status = job->FinalizeJob(shared_info, isolate);
if (status == CompilationJob::SUCCEEDED) {
InstallUnoptimizedCode(compilation_info, shared_info, isolate); // <--------给JSFunction安装feedback_metadata,code和BytecodeArray

MaybeHandle<CoverageInfo> coverage_info;
if (compilation_info->has_coverage_info() &&
!shared_info->HasCoverageInfo()) {
coverage_info = compilation_info->coverage_info();
}

finalize_unoptimized_compilation_data_list->emplace_back(
isolate, shared_info, coverage_info, job->time_taken_to_execute(),
job->time_taken_to_finalize());
}
DCHECK_IMPLIES(status == CompilationJob::RETRY_ON_MAIN_THREAD,
(std::is_same<IsolateT, LocalIsolate>::value));
return status;
}

template <typename IsolateT>
void InstallUnoptimizedCode(UnoptimizedCompilationInfo* compilation_info,
Handle<SharedFunctionInfo> shared_info,
IsolateT* isolate) {
if (compilation_info->has_bytecode_array()) {
// ... //
shared_info->set_bytecode_array(*compilation_info->bytecode_array());

Handle<FeedbackMetadata> feedback_metadata = FeedbackMetadata::New(
isolate, compilation_info->feedback_vector_spec());
shared_info->set_feedback_metadata(*feedback_metadata, kReleaseStore);
} // ... //
}

在这个函数调用完成后,JSFunction的code变成了Code BUILTIN InterpreterEntryTrampoline

这里再调试的时候还可以发现,feedback_metadata带了每个slot的kind,测试代码及安装好的feedback_metadata:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 测试使用的js代码
function foo(a){
a.x = 1.2;
return a.x + a.y;
}
var c = {x:1.1,y:2.2};
foo(c);

// 生成的字节码
[generated bytecode for function: foo (0x32a10829348d <SharedFunctionInfo foo>)]
Bytecode length: 19
Parameter count 2
Register count 1
Frame size 8
OSR nesting level: 0
Bytecode Age: 0
0x32a1082936f6 @ 0 : 13 00 LdaConstant [0]
0x32a1082936f8 @ 2 : 32 03 01 00 StaNamedProperty a0, [1], [0]
0x32a1082936fc @ 6 : 2d 03 01 03 LdaNamedProperty a0, [1], [3]
0x32a108293700 @ 10 : c4 Star0
0x32a108293701 @ 11 : 2d 03 02 05 LdaNamedProperty a0, [2], [5]
0x32a108293705 @ 15 : 39 fa 02 Add r0, [2]
0x32a108293708 @ 18 : a9 Return

// 初始的feedback_metadata
pwndbg> job 0x37750829370d
0x37750829370d: [FeedbackMetadata] in OldSpace
- map: 0x377508002981 <Map>
- slot_count: 7
- create_closure_slot_count: 0
Slot #0 StoreNamedSloppy
Slot #2 BinaryOp
Slot #3 LoadProperty
Slot #5 LoadProperty

我猜测是再生成字节码的时候,就是调用visitxxxx函数(src/interpreter/bytecode-generator.cc)的时候填充的(如feedback_spec()->AddLoadICSlot();)

initialize feedbackCell

Cpmpiler::Compile编译好function的code之后,会初始化JSFunction对象的FeedbackCell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// static
bool Compiler::Compile(Isolate* isolate, Handle<JSFunction> function,
ClearExceptionFlag flag,
IsCompiledScope* is_compiled_scope) {
// ... //
Handle<SharedFunctionInfo> shared_info = handle(function->shared(), isolate);

// Ensure shared function info is compiled.
*is_compiled_scope = shared_info->is_compiled_scope(isolate);
if (!is_compiled_scope->is_compiled() &&
!Compile(isolate, shared_info, flag, is_compiled_scope)) { // 编译生成code
return false;
}
// ... //

// Initialize the feedback cell for this JSFunction and reset the interrupt
// budget for feedback vector allocation even if there is a closure feedback
// cell array. We are re-compiling when we have a closure feedback cell array
// which means we are compiling after a bytecode flush.
// TODO(verwaest/mythria): Investigate if allocating feedback vector
// immediately after a flush would be better.
JSFunction::InitializeFeedbackCell(function, is_compiled_scope, true);
// ... //
}

// src/objects/js-function.cc
// static
void JSFunction::InitializeFeedbackCell(
Handle<JSFunction> function, IsCompiledScope* is_compiled_scope,
bool reset_budget_for_feedback_allocation) {
Isolate* const isolate = function->GetIsolate();
#if V8_ENABLE_WEBASSEMBLY
// The following checks ensure that the feedback vectors are compatible with
// the feedback metadata. For Asm / Wasm functions we never allocate / use
// feedback vectors, so a mismatch between the metadata and feedback vector is
// harmless. The checks could fail for functions that has has_asm_wasm_broken
// set at runtime (for ex: failed instantiation).
if (function->shared().HasAsmWasmData()) return;
#endif // V8_ENABLE_WEBASSEMBLY

if (function->has_feedback_vector()) {
CHECK_EQ(function->feedback_vector().length(),
function->feedback_vector().metadata().slot_count());
return;
}

if (function->has_closure_feedback_cell_array()) {
CHECK_EQ(
function->closure_feedback_cell_array().length(),
function->shared().feedback_metadata().create_closure_slot_count());
}

const bool needs_feedback_vector =
!FLAG_lazy_feedback_allocation || FLAG_always_opt ||
// We also need a feedback vector for certain log events, collecting type
// profile and more precise code coverage.
FLAG_log_function_events || !isolate->is_best_effort_code_coverage() ||
isolate->is_collecting_type_profile();

if (needs_feedback_vector) {
EnsureFeedbackVector(function, is_compiled_scope);
} else {
EnsureClosureFeedbackCellArray(function,
reset_budget_for_feedback_allocation);
}
}

在JSFunction::InitializeFeedbackCell中一般会走到EnsureClosureFeedbackCellArray:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// static
void JSFunction::EnsureClosureFeedbackCellArray(
Handle<JSFunction> function, bool reset_budget_for_feedback_allocation) {
// ... //

bool has_closure_feedback_cell_array =
(function->has_closure_feedback_cell_array() ||
function->has_feedback_vector());
// Initialize the interrupt budget to the feedback vector allocation budget
// when initializing the feedback cell for the first time or after a bytecode
// flush. We retain the closure feedback cell array on bytecode flush, so
// reset_budget_for_feedback_allocation is used to reset the budget in these
// cases. When using a fixed allocation budget, we reset it on a bytecode
// flush so no additional initialization is required here.
if (V8_UNLIKELY(FLAG_feedback_allocation_on_bytecode_size) &&
(reset_budget_for_feedback_allocation ||
!has_closure_feedback_cell_array)) {
function->SetInterruptBudget(); // <-- 这里会设置feedback_cell的interrupt_budget
}
// .. //
}

// src/objects/js-function-inl.h
void JSFunction::SetInterruptBudget() {
if (!has_feedback_vector()) {
DCHECK(shared().is_compiled());
int budget = FLAG_budget_for_feedback_vector_allocation;
if (FLAG_feedback_allocation_on_bytecode_size) {
budget = shared().GetBytecodeArray(GetIsolate()).length() *
FLAG_scale_factor_for_feedback_allocation; // <-- bytecodeLength * 8
}
raw_feedback_cell().set_interrupt_budget(budget);
return;
}
FeedbackVector::SetInterruptBudget(raw_feedback_cell());
}

注意这里的budget是bytecodeLength的长度乘以8,这也就是为什么第九次才安装feedback_vector,这个后面会解释

InterpreterEntryTrampoline

测试平台是x64,所以这里应该是src/builtins/x64/builtins-x64.cc下的 Generate_InterpreterEntryTrampoline函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
void Builtins::Generate_InterpreterEntryTrampoline(MacroAssembler* masm) {
Register closure = rdi;
Register feedback_vector = rbx;

// Get the bytecode array from the function object and load it into
// kInterpreterBytecodeArrayRegister.
__ LoadTaggedPointerField(
kScratchRegister,
FieldOperand(closure, JSFunction::kSharedFunctionInfoOffset));
__ LoadTaggedPointerField(
kInterpreterBytecodeArrayRegister,
FieldOperand(kScratchRegister, SharedFunctionInfo::kFunctionDataOffset));

Label is_baseline;
GetSharedFunctionInfoBytecodeOrBaseline(
masm, kInterpreterBytecodeArrayRegister, kScratchRegister, &is_baseline);

// The bytecode array could have been flushed from the shared function info,
// if so, call into CompileLazy.
Label compile_lazy;
__ CmpObjectType(kInterpreterBytecodeArrayRegister, BYTECODE_ARRAY_TYPE,
kScratchRegister);
__ j(not_equal, &compile_lazy);

// Load the feedback vector from the closure.
__ LoadTaggedPointerField(
feedback_vector, FieldOperand(closure, JSFunction::kFeedbackCellOffset));
__ LoadTaggedPointerField(feedback_vector,
FieldOperand(feedback_vector, Cell::kValueOffset));

Label push_stack_frame;
// Check if feedback vector is valid. If valid, check for optimized code
// and update invocation count. Otherwise, setup the stack frame.
__ LoadMap(rcx, feedback_vector);
__ CmpInstanceType(rcx, FEEDBACK_VECTOR_TYPE);
__ j(not_equal, &push_stack_frame);

// Check for an optimization marker.
Label has_optimized_code_or_marker;
Register optimization_state = rcx;
LoadOptimizationStateAndJumpIfNeedsProcessing(
masm, optimization_state, feedback_vector, &has_optimized_code_or_marker);

Label not_optimized;
__ bind(&not_optimized);

// Increment invocation count for the function.
__ incl(
FieldOperand(feedback_vector, FeedbackVector::kInvocationCountOffset));

// Open a frame scope to indicate that there is a frame on the stack. The
// MANUAL indicates that the scope shouldn't actually generate code to set up
// the frame (that is done below).
__ bind(&push_stack_frame);
FrameScope frame_scope(masm, StackFrame::MANUAL);
__ pushq(rbp); // Caller's frame pointer.
__ movq(rbp, rsp);
__ Push(kContextRegister); // Callee's context.
__ Push(kJavaScriptCallTargetRegister); // Callee's JS function.
__ Push(kJavaScriptCallArgCountRegister); // Actual argument count.

// Reset code age and the OSR arming. The OSR field and BytecodeAgeOffset are
// 8-bit fields next to each other, so we could just optimize by writing a
// 16-bit. These static asserts guard our assumption is valid.
STATIC_ASSERT(BytecodeArray::kBytecodeAgeOffset ==
BytecodeArray::kOsrLoopNestingLevelOffset + kCharSize);
STATIC_ASSERT(BytecodeArray::kNoAgeBytecodeAge == 0);
__ movw(FieldOperand(kInterpreterBytecodeArrayRegister,
BytecodeArray::kOsrLoopNestingLevelOffset),
Immediate(0));

// Load initial bytecode offset.
__ Move(kInterpreterBytecodeOffsetRegister,
BytecodeArray::kHeaderSize - kHeapObjectTag);

// Push bytecode array and Smi tagged bytecode offset.
__ Push(kInterpreterBytecodeArrayRegister);
__ SmiTag(rcx, kInterpreterBytecodeOffsetRegister);
__ Push(rcx);

// Allocate the local and temporary register file on the stack.
Label stack_overflow;
{
// Load frame size from the BytecodeArray object.
__ movl(rcx, FieldOperand(kInterpreterBytecodeArrayRegister,
BytecodeArray::kFrameSizeOffset));

// Do a stack check to ensure we don't go over the limit.
__ movq(rax, rsp);
__ subq(rax, rcx);
__ cmpq(rax, __ StackLimitAsOperand(StackLimitKind::kRealStackLimit));
__ j(below, &stack_overflow);

// If ok, push undefined as the initial value for all register file entries.
Label loop_header;
Label loop_check;
__ LoadRoot(kInterpreterAccumulatorRegister, RootIndex::kUndefinedValue);
__ j(always, &loop_check, Label::kNear);
__ bind(&loop_header);
// TODO(rmcilroy): Consider doing more than one push per loop iteration.
__ Push(kInterpreterAccumulatorRegister);
// Continue loop if not done.
__ bind(&loop_check);
__ subq(rcx, Immediate(kSystemPointerSize));
__ j(greater_equal, &loop_header, Label::kNear);
}

// If the bytecode array has a valid incoming new target or generator object
// register, initialize it with incoming value which was passed in rdx.
Label no_incoming_new_target_or_generator_register;
__ movsxlq(
rcx,
FieldOperand(kInterpreterBytecodeArrayRegister,
BytecodeArray::kIncomingNewTargetOrGeneratorRegisterOffset));
__ testl(rcx, rcx);
__ j(zero, &no_incoming_new_target_or_generator_register, Label::kNear);
__ movq(Operand(rbp, rcx, times_system_pointer_size, 0), rdx);
__ bind(&no_incoming_new_target_or_generator_register);

// Perform interrupt stack check.
// TODO(solanes): Merge with the real stack limit check above.
Label stack_check_interrupt, after_stack_check_interrupt;
__ cmpq(rsp, __ StackLimitAsOperand(StackLimitKind::kInterruptStackLimit));
__ j(below, &stack_check_interrupt);
__ bind(&after_stack_check_interrupt);

// The accumulator is already loaded with undefined.

// Load the dispatch table into a register and dispatch to the bytecode
// handler at the current bytecode offset.
Label do_dispatch;
__ bind(&do_dispatch);
__ Move(
kInterpreterDispatchTableRegister,
ExternalReference::interpreter_dispatch_table_address(masm->isolate()));
__ movzxbq(kScratchRegister,
Operand(kInterpreterBytecodeArrayRegister,
kInterpreterBytecodeOffsetRegister, times_1, 0));
__ movq(kJavaScriptCallCodeStartRegister,
Operand(kInterpreterDispatchTableRegister, kScratchRegister,
times_system_pointer_size, 0));
__ call(kJavaScriptCallCodeStartRegister);
masm->isolate()->heap()->SetInterpreterEntryReturnPCOffset(masm->pc_offset());

// Any returns to the entry trampoline are either due to the return bytecode
// or the interpreter tail calling a builtin and then a dispatch.

// Get bytecode array and bytecode offset from the stack frame.
__ movq(kInterpreterBytecodeArrayRegister,
Operand(rbp, InterpreterFrameConstants::kBytecodeArrayFromFp));
__ SmiUntag(kInterpreterBytecodeOffsetRegister,
Operand(rbp, InterpreterFrameConstants::kBytecodeOffsetFromFp));

// Either return, or advance to the next bytecode and dispatch.
Label do_return;
__ movzxbq(rbx, Operand(kInterpreterBytecodeArrayRegister,
kInterpreterBytecodeOffsetRegister, times_1, 0));
AdvanceBytecodeOffsetOrReturn(masm, kInterpreterBytecodeArrayRegister,
kInterpreterBytecodeOffsetRegister, rbx, rcx,
r8, &do_return);
__ jmp(&do_dispatch);

__ bind(&do_return);
// The return value is in rax.
LeaveInterpreterFrame(masm, rbx, rcx);
__ ret(0);

// ... //
}

当成汇编看就好,其中Label do_dispatch里的__ call(kJavaScriptCallCodeStartRegister);就是跳到第一个字节码的处理函数开始执行JSFuntion了

LdaNamedProperty

示例中的第一个字节码是LdaNamedProperty:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// src/interpreter/interpreter-generator.cc
// LdaNamedProperty <object> <name_index> <slot>
//
// Calls the LoadIC at FeedBackVector slot <slot> for <object> and the name at
// constant pool entry <name_index>.
IGNITION_HANDLER(LdaNamedProperty, InterpreterAssembler) {
TNode<HeapObject> feedback_vector = LoadFeedbackVector();

// Load receiver.
TNode<Object> recv = LoadRegisterAtOperandIndex(0);

// Load the name and context lazily.
LazyNode<TaggedIndex> lazy_slot = [=] {
return BytecodeOperandIdxTaggedIndex(2);
};
LazyNode<Name> lazy_name = [=] {
return CAST(LoadConstantPoolEntryAtOperandIndex(1));
};
LazyNode<Context> lazy_context = [=] { return GetContext(); };

Label done(this);
TVARIABLE(Object, var_result);
ExitPoint exit_point(this, &done, &var_result);

AccessorAssembler::LazyLoadICParameters params(lazy_context, recv, lazy_name,
lazy_slot, feedback_vector);
AccessorAssembler accessor_asm(state());
accessor_asm.LoadIC_BytecodeHandler(&params, &exit_point);

BIND(&done);
{
SetAccumulator(var_result.value());
Dispatch();
}
}

LdaNamedProperty的字节码处理函数下load feedbackvector,在load receiver,name,context和slot,最后把处理流程全部转给LoadIC_BytecodeHandler进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void AccessorAssembler::LoadIC_BytecodeHandler(const LazyLoadICParameters* p,
ExitPoint* exit_point) {
// Must be kept in sync with LoadIC.

// This function is hand-tuned to omit frame construction for common cases,
// e.g.: monomorphic field and constant loads through smi handlers.
// Polymorphic ICs with a hit in the first two entries also omit frames.
// TODO(jgruber): Frame omission is fragile and can be affected by minor
// changes in control flow and logic. We currently have no way of ensuring
// that no frame is constructed, so it's easy to break this optimization by
// accident.
Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred),
no_feedback(this, Label::kDeferred);

GotoIf(IsUndefined(p->vector()), &no_feedback);

// ... //
BIND(&no_feedback);
{
Comment("LoadIC_BytecodeHandler_nofeedback");
// Call into the stub that implements the non-inlined parts of LoadIC.
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtin::kLoadIC_NoFeedback),
p->context(), p->receiver(), p->name(),
SmiConstant(FeedbackSlotKind::kLoadProperty));
}
// ... //
}

该函数会先检查JSFunction是否有feedback vector,没有的话就调用LoadIC_NoFeedback来进行处理,我们是第一次调用,所以没有feedback vector,所以直接到Builtin::kLoadIC_NoFeedback :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void AccessorAssembler::LoadIC_NoFeedback(const LoadICParameters* p,
TNode<Smi> ic_kind) {
Label miss(this, Label::kDeferred);
TNode<Object> lookup_start_object = p->receiver_and_lookup_start_object();
GotoIf(TaggedIsSmi(lookup_start_object), &miss);
TNode<Map> lookup_start_object_map = LoadMap(CAST(lookup_start_object));
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);

TNode<Uint16T> instance_type = LoadMapInstanceType(lookup_start_object_map);

{
// Special case for Function.prototype load, because it's very common
// for ICs that are only executed once (MyFunc.prototype.foo = ...).
Label not_function_prototype(this, Label::kDeferred);
GotoIfNot(IsJSFunctionInstanceType(instance_type), &not_function_prototype);
GotoIfNot(IsPrototypeString(p->name()), &not_function_prototype);

GotoIfPrototypeRequiresRuntimeLookup(CAST(lookup_start_object),
lookup_start_object_map,
&not_function_prototype);
Return(LoadJSFunctionPrototype(CAST(lookup_start_object), &miss));
BIND(&not_function_prototype);
}

GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map,
instance_type, p, &miss, kDontUseStubCache); // <-----

BIND(&miss);
{
TailCallRuntime(Runtime::kLoadNoFeedbackIC_Miss, p->context(),
p->receiver(), p->name(), ic_kind);
}
}

LoadIC_NoFeedback 主要是通过GenericPropertyLoad函数来进行属性的读取

Return

return字节码通常来说就是JSFunction的最后一个字节码,该字节码会更新JSFunction里的一些值:

1
2
3
4
5
6
// Return the value in the accumulator.
IGNITION_HANDLER(Return, InterpreterAssembler) {
UpdateInterruptBudgetOnReturn();
TNode<Object> accumulator = GetAccumulator();
Return(accumulator);
}

主要内容都在UpdateInterruptBudgetOnReturn里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// src/interpreter/interpreter-assembler.cc
void InterpreterAssembler::UpdateInterruptBudgetOnReturn() {
// Update profiling count by the number of bytes between the end of the
// current bytecode and the start of the first one, to simulate backedge to
// start of function.
//
// With headers and current offset, the bytecode array layout looks like:
//
// <---------- simulated backedge ----------
// | header | first bytecode | .... | return bytecode |
// |<------ current offset ------->
// ^ tagged bytecode array pointer
//
// UpdateInterruptBudget already handles adding the bytecode size to the
// length of the back-edge, so we just have to correct for the non-zero offset
// of the first bytecode.

const int kFirstBytecodeOffset = BytecodeArray::kHeaderSize - kHeapObjectTag;
TNode<Int32T> profiling_weight =
Int32Sub(TruncateIntPtrToInt32(BytecodeOffset()),
Int32Constant(kFirstBytecodeOffset));
UpdateInterruptBudget(profiling_weight, true);
}

void InterpreterAssembler::UpdateInterruptBudget(TNode<Int32T> weight,
bool backward) {
Comment("[ UpdateInterruptBudget");

// Assert that the weight is positive (negative weights should be implemented
// as backward updates).
CSA_DCHECK(this, Int32GreaterThanOrEqual(weight, Int32Constant(0)));

Label load_budget_from_bytecode(this), load_budget_done(this);
TNode<JSFunction> function = CAST(LoadRegister(Register::function_closure()));
TNode<FeedbackCell> feedback_cell =
LoadObjectField<FeedbackCell>(function, JSFunction::kFeedbackCellOffset);
TNode<Int32T> old_budget = LoadObjectField<Int32T>(
feedback_cell, FeedbackCell::kInterruptBudgetOffset);

// Make sure we include the current bytecode in the budget calculation.
TNode<Int32T> budget_after_bytecode =
Int32Sub(old_budget, Int32Constant(CurrentBytecodeSize()));

Label done(this);
TVARIABLE(Int32T, new_budget);
if (backward) {
// Update budget by |weight| and check if it reaches zero.
new_budget = Int32Sub(budget_after_bytecode, weight);
TNode<BoolT> condition =
Int32GreaterThanOrEqual(new_budget.value(), Int32Constant(0));
Label ok(this), interrupt_check(this, Label::kDeferred);
Branch(condition, &ok, &interrupt_check);

BIND(&interrupt_check);
// JumpLoop should do a stack check as part of the interrupt.
CallRuntime(
bytecode() == Bytecode::kJumpLoop
? Runtime::kBytecodeBudgetInterruptWithStackCheckFromBytecode
: Runtime::kBytecodeBudgetInterruptFromBytecode,
GetContext(), function);
Goto(&done);

BIND(&ok);
} else {
// For a forward jump, we know we only increase the interrupt budget, so
// no need to check if it's below zero.
new_budget = Int32Add(budget_after_bytecode, weight);
}

// Update budget.
StoreObjectFieldNoWriteBarrier(
feedback_cell, FeedbackCell::kInterruptBudgetOffset, new_budget.value());
Goto(&done);
BIND(&done);
Comment("] UpdateInterruptBudget");
}

UpdateInterruptBudgetOnReturn 函数会计算出一个profiling_weight,这个profiling_weight好像正常情况下等于bytecodeArray的长度,本例子中就是5;

接着在UpdateInterruptBudget更新feedbackCell里的InterruptBudget,如果new_budget大于0,就更新InterruptBudget,如果小于0,那么就会调用:

1
2
3
4
5
6
7
BIND(&interrupt_check);
// JumpLoop should do a stack check as part of the interrupt.
CallRuntime(
bytecode() == Bytecode::kJumpLoop
? Runtime::kBytecodeBudgetInterruptWithStackCheckFromBytecode
: Runtime::kBytecodeBudgetInterruptFromBytecode,
GetContext(), function);

runtime BytecodeBudgetInterruptWithStackCheckFromBytecode函数只是前面做了一些check,最终和runtime BytecodeBudgetInterruptFromBytecode一样都会调用到BytecodeBudgetInterruptFromBytecode 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/runtime/runtime-internal.cc
void BytecodeBudgetInterruptFromBytecode(Isolate* isolate,
Handle<JSFunction> function) {
function->SetInterruptBudget();
bool should_mark_for_optimization = function->has_feedback_vector();
if (!function->has_feedback_vector()) {
IsCompiledScope is_compiled_scope(
function->shared().is_compiled_scope(isolate));
JSFunction::EnsureFeedbackVector(function, &is_compiled_scope); // [1]
DCHECK(is_compiled_scope.is_compiled());
// Also initialize the invocation count here. This is only really needed for
// OSR. When we OSR functions with lazy feedback allocation we want to have
// a non zero invocation count so we can inline functions.
function->feedback_vector().set_invocation_count(1, kRelaxedStore);
}
if (CanCompileWithBaseline(isolate, function->shared()) &&
!function->ActiveTierIsBaseline()) {
if (FLAG_baseline_batch_compilation) {
isolate->baseline_batch_compiler()->EnqueueFunction(function);
} else {
IsCompiledScope is_compiled_scope(
function->shared().is_compiled_scope(isolate));
Compiler::CompileBaseline(isolate, function, Compiler::CLEAR_EXCEPTION,
&is_compiled_scope);
}
}
if (should_mark_for_optimization) {
SealHandleScope shs(isolate);
isolate->counters()->runtime_profiler_ticks()->Increment();
isolate->runtime_profiler()->MarkCandidatesForOptimizationFromBytecode();
}
}

该函数在[1]处给JSFunction安装FeedbackVector,也就是说InterruptBudget要小于0才安装FeedbackVector,前面说过初始化的时候InterruptBudget的值为BytecodeArray的长度乘以8,这也就是说正常情况下就是在第九次调用的时候会给JSFunction安装FeedbackVector

第10次调用

LoadIC_BytecodeHandler

在第9次调用的时候return字节码给JSFunction安装了FeedbackVector,那么第10次的LoadIC_BytecodeHandler就会走rutime 的LoadIC_Miss:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void AccessorAssembler::LoadIC_BytecodeHandler(const LazyLoadICParameters* p,
ExitPoint* exit_point) {
// Must be kept in sync with LoadIC.

// This function is hand-tuned to omit frame construction for common cases,
// e.g.: monomorphic field and constant loads through smi handlers.
// Polymorphic ICs with a hit in the first two entries also omit frames.
// TODO(jgruber): Frame omission is fragile and can be affected by minor
// changes in control flow and logic. We currently have no way of ensuring
// that no frame is constructed, so it's easy to break this optimization by
// accident.
Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred),
no_feedback(this, Label::kDeferred);

GotoIf(IsUndefined(p->vector()), &no_feedback);

TNode<Map> lookup_start_object_map =
LoadReceiverMap(p->receiver_and_lookup_start_object());
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);

// Inlined fast path.
{
Comment("LoadIC_BytecodeHandler_fast");

TVARIABLE(MaybeObject, var_handler);
Label try_polymorphic(this), if_handler(this, &var_handler);

TNode<MaybeObject> feedback = TryMonomorphicCase(
p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler,
&var_handler, &try_polymorphic);

BIND(&if_handler);
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point);

BIND(&try_polymorphic);
{
TNode<HeapObject> strong_feedback =
GetHeapObjectIfStrong(feedback, &miss);
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call);
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback),
&if_handler, &var_handler, &miss);
}
}

// ... //

BIND(&miss);
{
Comment("LoadIC_BytecodeHandler_miss");

exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context(), // <--------
p->receiver(), p->name(), p->slot(),
p->vector());
}
}

Runtime_LoadIC_Miss

主要还是因为用于优化属性读取的handler还没有安装,所以流程会走到miss标签处,runtime LoadIC_Miss函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/ic/ic.cc
RUNTIME_FUNCTION(Runtime_LoadIC_Miss) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
// Runtime functions don't follow the IC's calling convention.
Handle<Object> receiver = args.at(0);
Handle<Name> key = args.at<Name>(1);
Handle<TaggedIndex> slot = args.at<TaggedIndex>(2);
Handle<FeedbackVector> vector = args.at<FeedbackVector>(3);
FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value());

// A monomorphic or polymorphic KeyedLoadIC with a string key can call the
// LoadIC miss handler if the handler misses. Since the vector Nexus is
// set up outside the IC, handle that here.
FeedbackSlotKind kind = vector->GetKind(vector_slot);
if (IsLoadICKind(kind)) {
LoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));

} else if (IsLoadGlobalICKind(kind)) {
DCHECK_EQ(isolate->native_context()->global_proxy(), *receiver);
receiver = isolate->global_object();
LoadGlobalIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(key));

} else {
DCHECK(IsKeyedLoadICKind(kind));
KeyedLoadIC ic(isolate, vector, vector_slot, kind);
ic.UpdateState(receiver, key);
RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key));
}
}

ic.UpdateState

先判断kind,然后调用ic.UpdateState:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void IC::UpdateState(Handle<Object> lookup_start_object, Handle<Object> name) {
if (state() == NO_FEEDBACK) return;
update_lookup_start_object_map(lookup_start_object);
if (!name->IsString()) return;
if (state() != MONOMORPHIC && state() != POLYMORPHIC) return;
if (lookup_start_object->IsNullOrUndefined(isolate())) return;

// Remove the target from the code cache if it became invalid
// because of changes in the prototype chain to avoid hitting it
// again.
if (ShouldRecomputeHandler(Handle<String>::cast(name))) {
MarkRecomputeHandler(name);
}
}

state的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// State for inline cache call sites. Aliased as IC::State.
enum class InlineCacheState {
// No feedback will be collected.
NO_FEEDBACK,
// Has never been executed.
UNINITIALIZED,
// Has been executed and only one receiver type has been seen.
MONOMORPHIC,
// Check failed due to prototype (or map deprecation).
RECOMPUTE_HANDLER,
// Multiple receiver types have been seen.
POLYMORPHIC,
// Many DOM receiver types have been seen for the same accessor.
MEGADOM,
// Many receiver types have been seen.
MEGAMORPHIC,
// A generic handler is installed and no extra typefeedback is recorded.
GENERIC,
};

LoadIC::Load

更新完state之后,调用LoadIC::Load:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name,
bool update_feedback,
Handle<Object> receiver) {
bool use_ic = (state() != NO_FEEDBACK) && FLAG_use_ic && update_feedback;

if (receiver.is_null()) {
receiver = object;
}

// ... //
PropertyKey key(isolate(), name);
LookupIterator it = LookupIterator(isolate(), receiver, key, object);

// Named lookup in the object.
LookupForRead(&it, IsAnyHas());

// ... //
if (it.IsFound() || !ShouldThrowReferenceError()) {
// Update inline cache and stub cache.
if (use_ic) {
UpdateCaches(&it); // [2]
} else if (state() == NO_FEEDBACK) {
// Tracing IC stats
IsLoadGlobalIC() ? TraceIC("LoadGlobalIC", name)
: TraceIC("LoadIC", name);
}

if (IsAnyHas()) {
// Named lookup in the object.
Maybe<bool> maybe = JSReceiver::HasProperty(&it);
if (maybe.IsNothing()) return MaybeHandle<Object>();
return maybe.FromJust() ? ReadOnlyRoots(isolate()).true_value_handle()
: ReadOnlyRoots(isolate()).false_value_handle();
}

// Get the property.
Handle<Object> result;

ASSIGN_RETURN_ON_EXCEPTION(
isolate(), result, Object::GetProperty(&it, IsLoadGlobalIC()), Object);
if (it.IsFound()) {
return result;
} else if (!ShouldThrowReferenceError()) {
LOG(isolate(), SuspectReadEvent(*name, *object));
return result;
}
}
return ReferenceError(name);
}

最关键的再于[2]处的UpdateCaches:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void LoadIC::UpdateCaches(LookupIterator* lookup) {
Handle<Object> handler;
if (lookup->state() == LookupIterator::ACCESS_CHECK) {
// ... //
} else if (!lookup->IsFound()) {
// ... //
} else if (IsLoadGlobalIC() && lookup->state() == LookupIterator::JSPROXY) {
// ... //
} else {
if (IsLoadGlobalIC()) {
// ... //
}
handler = ComputeHandler(lookup);
}
// Can't use {lookup->name()} because the LookupIterator might be in
// "elements" mode for keys that are strings representing integers above
// JSArray::kMaxIndex.
SetCache(lookup->GetName(), handler);
TraceIC("LoadIC", lookup->GetName());
}

ComputeHandler

handler由ComputeHandler计算得出,ComputeHandler函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
Handle<Object> receiver = lookup->GetReceiver();
ReadOnlyRoots roots(isolate());

Handle<Object> lookup_start_object = lookup->lookup_start_object();
// ... //
Handle<Map> map = lookup_start_object_map();
bool holder_is_lookup_start_object =
lookup_start_object.is_identical_to(lookup->GetHolder<JSReceiver>());

switch (lookup->state()) {
case LookupIterator::INTERCEPTOR: {
// ... //
}

case LookupIterator::ACCESSOR: {
// ... //
}

case LookupIterator::DATA: {
Handle<JSReceiver> holder = lookup->GetHolder<JSReceiver>();
DCHECK_EQ(PropertyKind::kData, lookup->property_details().kind());
Handle<Smi> smi_handler;
if (lookup->is_dictionary_holder()) {
// ... //
} else if (lookup->IsElement(*holder)) {
// ... //
} else {
// ... //
{
DCHECK(holder->IsJSObject(isolate()));
FieldIndex field = lookup->GetFieldIndex();
smi_handler = LoadHandler::LoadField(isolate(), field);
}
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldDH);
if (holder_is_lookup_start_object) return smi_handler;
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldFromPrototypeDH);
}
// ... //
}
case LookupIterator::INTEGER_INDEXED_EXOTIC:
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadIntegerIndexedExoticDH);
return LoadHandler::LoadNonExistent(isolate());

case LookupIterator::JSPROXY: {
// ... //
}
case LookupIterator::ACCESS_CHECK:
case LookupIterator::NOT_FOUND:
case LookupIterator::TRANSITION:
UNREACHABLE();
}

return Handle<Code>::null();
}

例子中走到的是LookupIterator::DATA分支,使用LoadHandler::LoadField创建了一个smi_handler:

1
2
3
4
5
6
7
Handle<Smi> LoadHandler::LoadField(Isolate* isolate, FieldIndex field_index) {
int config = KindBits::encode(Kind::kField) |
IsInobjectBits::encode(field_index.is_inobject()) |
IsDoubleBits::encode(field_index.is_double()) |
FieldIndexBits::encode(field_index.index());
return handle(Smi::FromInt(config), isolate);
}

这个handler里应该记录了该属性的偏移,后续靠这个handler里记录的信息应该可以直接通过偏移取到想要查找的属性(当然前提是map要正确),

计算完并得到handler之后,LoadIC::UpdateCaches函数会调用SetCache函数来设置对该属性的cache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void IC::SetCache(Handle<Name> name, const MaybeObjectHandle& handler) {
DCHECK(IsHandler(*handler));
// Currently only load and store ICs support non-code handlers.
DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas());
switch (state()) {
case NO_FEEDBACK:
UNREACHABLE();
case UNINITIALIZED:
UpdateMonomorphicIC(handler, name);
break;
case RECOMPUTE_HANDLER:
case MONOMORPHIC:
if (IsGlobalIC()) {
UpdateMonomorphicIC(handler, name);
break;
}
V8_FALLTHROUGH;
case POLYMORPHIC:
if (UpdatePolymorphicIC(name, handler)) break;
if (UpdateMegaDOMIC(handler, name)) break;
if (!is_keyed() || state() == RECOMPUTE_HANDLER) {
CopyICToMegamorphicCache(name);
}
V8_FALLTHROUGH;
case MEGADOM:
ConfigureVectorState(MEGAMORPHIC, name);
V8_FALLTHROUGH;
case MEGAMORPHIC:
UpdateMegamorphicCache(lookup_start_object_map(), name, handler);
// Indicate that we've handled this case.
vector_set_ = true;
break;
case GENERIC:
UNREACHABLE();
}
}

我们这是第一次,所以走的是UNINITIALIZED分支,调用的是UpdateMonomorphicIC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void IC::UpdateMonomorphicIC(const MaybeObjectHandle& handler,
Handle<Name> name) {
DCHECK(IsHandler(*handler));
ConfigureVectorState(name, lookup_start_object_map(), handler);
}
/////////////////////////////////////////////////////////////////////
void IC::ConfigureVectorState(Handle<Name> name, Handle<Map> map,
const MaybeObjectHandle& handler) {
if (IsGlobalIC()) {
nexus()->ConfigureHandlerMode(handler);
} else {
// Non-keyed ICs don't track the name explicitly.
if (!is_keyed()) name = Handle<Name>::null();
nexus()->ConfigureMonomorphic(name, map, handler);
}

OnFeedbackChanged(IsLoadGlobalIC() ? "LoadGlobal" : "Monomorphic");
}
/////////////////////////////////////////////////////////////////////
void FeedbackNexus::ConfigureMonomorphic(Handle<Name> name,
Handle<Map> receiver_map,
const MaybeObjectHandle& handler) {
DCHECK(handler.is_null() || IC::IsHandler(*handler));
if (kind() == FeedbackSlotKind::kStoreDataPropertyInLiteral) {
SetFeedback(HeapObjectReference::Weak(*receiver_map), UPDATE_WRITE_BARRIER,
*name);
} else {
if (name.is_null()) {
SetFeedback(HeapObjectReference::Weak(*receiver_map),
UPDATE_WRITE_BARRIER, *handler);
} else {
Handle<WeakFixedArray> array = CreateArrayOfSize(2);
array->Set(0, HeapObjectReference::Weak(*receiver_map));
array->Set(1, *handler);
SetFeedback(*name, UPDATE_WRITE_BARRIER, *array);
}
}
}
/////////////////////////////////////////////////////////////////////
template <typename FeedbackType, typename FeedbackExtraType>
void FeedbackNexus::SetFeedback(FeedbackType feedback, WriteBarrierMode mode,
FeedbackExtraType feedback_extra,
WriteBarrierMode mode_extra) {
static_assert(IsValidFeedbackType<FeedbackType>(),
"feedbacks need to be Smi, Object or MaybeObject");
static_assert(IsValidFeedbackType<FeedbackExtraType>(),
"feedbacks need to be Smi, Object or MaybeObject");
MaybeObject fmo = MaybeObject::Create(feedback);
MaybeObject fmo_extra = MaybeObject::Create(feedback_extra);
config()->SetFeedbackPair(vector(), slot(), fmo, mode, fmo_extra, mode_extra);
}

这么一通下来,config()->SetFeedbackPair更新了JSFunction的FeedbackVector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> job 0x3a11082936b5
0x3a11082936b5: [FeedbackVector] in OldSpace
- map: 0x3a1108002715 <Map>
- length: 2
- shared function info: 0x3a110829347d <SharedFunctionInfo foo>
- no optimized code
- optimization marker: OptimizationMarker::kNone
- optimization tier: OptimizationTier::kNone
- invocation count: 2
- profiler ticks: 0
- closure feedback cell array: 0x3a11080033d1: [ClosureFeedbackCellArray] in ReadOnlySpace
- map: 0x3a1108002959 <Map>
- length: 0

- slot #0 LoadProperty MONOMORPHIC {
[0]: [weak] 0x3a11082c7b91 <Map(HOLEY_ELEMENTS)>
[1]: 1924
}

slot #0里的[0] 是对象的map,[1]是smihandler(就是前面计算出来的handler)

更新好FeedbackVector后,LoadIC::Load指令会调用Object::GetProperty来读取属性的值

第11次调用

还是回到LoadIC_BytecodeHandler,这次我们传入的对象的map还是没变,所以TryMonomorphicCase会在FeedbackVector中找到handler,那么流程就会走到:

1
2
BIND(&if_handler);
HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point);

HandleLoadICHandlerCase函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/ic/accessor-assembler.cc
void AccessorAssembler::HandleLoadICHandlerCase(
const LazyLoadICParameters* p, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements, LoadAccessMode access_mode) {
// ... //

Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler);

BIND(&try_proto_handler);
{
// ... //
}

// |handler| is a Smi, encoding what to do. See SmiHandler methods
// for the encoding format.
BIND(&if_smi_handler);
{
HandleLoadICSmiHandlerCase(
p, var_holder.value(), CAST(var_smi_handler.value()), handler, miss,
exit_point, ic_mode, on_nonexistent, support_elements, access_mode);
}

BIND(&call_handler);
{
// ... //
}
}

例子中创建的就是smi_handler,所以流程会走到if_smi_handler处,调用HandleLoadICSmiHandlerCase,由于我们的accessmode是LoadAccessMode::kLoad(这个是默认值),该函数会调用HandleLoadICSmiHandlerLoadNamedCase来处理属性的读取:

AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase(
const LazyLoadICParameters* p, TNode<Object> holder,
TNode<IntPtrT> handler_kind, TNode<WordT> handler_word, Label* rebox_double,
TVariable<Float64T>* var_double_value, TNode<Object> handler, Label* miss,
ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent,
ElementSupport support_elements) {
Label constant(this), field(this), normal(this, Label::kDeferred),
slow(this, Label::kDeferred), interceptor(this, Label::kDeferred),
nonexistent(this), accessor(this, Label::kDeferred),
global(this, Label::kDeferred), module_export(this, Label::kDeferred),
proxy(this, Label::kDeferred),
native_data_property(this, Label::kDeferred),
api_getter(this, Label::kDeferred);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kField)), &field);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kConstantFromPrototype)), &constant);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kNonExistent)), &nonexistent);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kNormal)), &normal);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kAccessor)), &accessor);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kNativeDataProperty)),
&native_data_property);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kApiGetter)), &api_getter);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kApiGetterHolderIsPrototype)),
&api_getter);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kGlobal)), &global);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kSlow)), &slow);

GotoIf(WordEqual(handler_kind, LOAD_KIND(kProxy)), &proxy);

Branch(WordEqual(handler_kind, LOAD_KIND(kModuleExport)), &module_export,
&interceptor);

BIND(&field);
{
// ... //

HandleLoadField(CAST(holder), handler_word, var_double_value, rebox_double,
miss, exit_point);

// ... //
}

// ... //
BIND(rebox_double);
exit_point->Return(AllocateHeapNumberWithValue(var_double_value->value()));
}

该函数就是定义了各种handler_kind和相对应的处理标签,对于本例子,我们会走到field标签,field标签里调用HandleLoadField来处理,该函数会根据smi_handler里存储的内容(前面encode在里面的各个标志位)来直接进行对属性的load,这里的直接就是说直接根据偏移来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void AccessorAssembler::HandleLoadField(TNode<JSObject> holder,
TNode<WordT> handler_word,
TVariable<Float64T>* var_double_value,
Label* rebox_double, Label* miss,
ExitPoint* exit_point) {
// ... //
LoadObjectField(holder, offset);
// ... //
}

TNode<T> LoadObjectField(TNode<HeapObject> object, int offset) {
const MachineType machine_type = offset == HeapObject::kMapOffset
? MachineType::MapInHeader()
: MachineTypeOf<T>::value;
return CAST(LoadFromObject(machine_type, object,
IntPtrConstant(offset - kHeapObjectTag)));
}

自此就拿到了属性,函数返回

MONOMORPHIC && POLYMORPHIC && MEGAMORPHIC

MONOMORPHIC 代表着inline cache只收到了一种map,POLYMORPHIC 是收到了多个map,如果遇到的map过多,inline cache的状态就会变成MEGAMORPHIC,这里可以参考inline cache state的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// State for inline cache call sites. Aliased as IC::State.
enum class InlineCacheState {
// No feedback will be collected.
NO_FEEDBACK,
// Has never been executed.
UNINITIALIZED,
// Has been executed and only one receiver type has been seen.
MONOMORPHIC,
// Check failed due to prototype (or map deprecation).
RECOMPUTE_HANDLER,
// Multiple receiver types have been seen.
POLYMORPHIC,
// Many DOM receiver types have been seen for the same accessor.
MEGADOM,
// Many receiver types have been seen.
MEGAMORPHIC,
// A generic handler is installed and no extra typefeedback is recorded.
GENERIC,
};

修改下测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function foo(a){
return a.x;
}

var c = {x:1.1};
var d = {x:c,y:2.2};
var e = {x:1,y:d,z:3};
var f = {x:2.2,y:1.1,z:e,u:1};
var g = {x:2.2,y:f,z:e,u:1,p:1};

for(var i = 0;i<11;i++){
foo(c);
}

%DebugPrint(foo);
%SystemBreak();

foo(d);
foo(e);
foo(f);

%DebugPrint(foo);
%SystemBreak();
foo(g);

%DebugPrint(foo);
%SystemBreak();

在创建好handler后,我们给foo函数传入一个与c对象有不同map的对象d

回到AccessorAssembler::LoadIC_BytecodeHandler函数,因为我们传入的对象d的map和handler里缓存的map不一致,所以流程会走到runtime的LoadIC_Miss:

1
2
3
4
5
6
7
8
BIND(&miss);
{
Comment("LoadIC_BytecodeHandler_miss");

exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context(),
p->receiver(), p->name(), p->slot(),
p->vector());
}

在前面第10次调用一节中讲过了Runtime_LoadIC_Miss,由于这次我们的state是MONOMORPHIC,所以ic.UpdateState函数会调用ShouldRecomputeHandler来查看inline cache中的handler对应的map是否还合法,不合法就把state设置为RECOMPUTE_HANDLER

接着流程又来到LoadIC:Load,先算出handler,在调用IC::SetCache来更新feedback vector,IC::SetCache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void IC::SetCache(Handle<Name> name, const MaybeObjectHandle& handler) {
DCHECK(IsHandler(*handler));
// Currently only load and store ICs support non-code handlers.
DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas());
switch (state()) {
case NO_FEEDBACK:
UNREACHABLE();
case UNINITIALIZED:
UpdateMonomorphicIC(handler, name);
break;
case RECOMPUTE_HANDLER:
case MONOMORPHIC:
if (IsGlobalIC()) {
UpdateMonomorphicIC(handler, name);
break;
}
V8_FALLTHROUGH;
case POLYMORPHIC:
if (UpdatePolymorphicIC(name, handler)) break; // <---这次调用的是这个
if (UpdateMegaDOMIC(handler, name)) break;
if (!is_keyed() || state() == RECOMPUTE_HANDLER) {
CopyICToMegamorphicCache(name);
}
V8_FALLTHROUGH;
case MEGADOM:
ConfigureVectorState(MEGAMORPHIC, name);
V8_FALLTHROUGH;
case MEGAMORPHIC:
UpdateMegamorphicCache(lookup_start_object_map(), name, handler);
// Indicate that we've handled this case.
vector_set_ = true;
break;
case GENERIC:
UNREACHABLE();
}
}

UpdatePolymorphicIC函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
bool IC::UpdatePolymorphicIC(Handle<Name> name,
const MaybeObjectHandle& handler) {
DCHECK(IsHandler(*handler));
if (is_keyed() && state() != RECOMPUTE_HANDLER) {
if (nexus()->GetName() != *name) return false;
}
Handle<Map> map = lookup_start_object_map();

std::vector<MapAndHandler> maps_and_handlers;
maps_and_handlers.reserve(FLAG_max_valid_polymorphic_map_count);
int deprecated_maps = 0;
int handler_to_overwrite = -1;

{
DisallowGarbageCollection no_gc;
int i = 0;
for (FeedbackIterator it(nexus()); !it.done(); it.Advance()) {
if (it.handler()->IsCleared()) continue;
MaybeObjectHandle existing_handler = handle(it.handler(), isolate());
Handle<Map> existing_map = handle(it.map(), isolate());

maps_and_handlers.push_back(
MapAndHandler(existing_map, existing_handler));

if (existing_map->is_deprecated()) {
// Filter out deprecated maps to ensure their instances get migrated.
deprecated_maps++;
} else if (map.is_identical_to(existing_map)) {
// ... //
if (handler.is_identical_to(existing_handler) &&
state() != RECOMPUTE_HANDLER) {
return false;
}

// ... //
handler_to_overwrite = i;
} else if (handler_to_overwrite == -1 &&
IsTransitionOfMonomorphicTarget(*existing_map, *map)) {
handler_to_overwrite = i;
}

i++;
}
DCHECK_LE(i, maps_and_handlers.size());
}

int number_of_maps = static_cast<int>(maps_and_handlers.size());
int number_of_valid_maps =
number_of_maps - deprecated_maps - (handler_to_overwrite != -1);

if (number_of_valid_maps >= FLAG_max_valid_polymorphic_map_count)
return false;
if (number_of_maps == 0 && state() != MONOMORPHIC && state() != POLYMORPHIC) {
return false;
}

number_of_valid_maps++;
if (number_of_valid_maps == 1) {
ConfigureVectorState(name, lookup_start_object_map(), handler);
} else {
if (is_keyed() && nexus()->GetName() != *name) return false;
if (handler_to_overwrite >= 0) {
maps_and_handlers[handler_to_overwrite].second = handler;
if (!map.is_identical_to(
maps_and_handlers.at(handler_to_overwrite).first)) {
maps_and_handlers[handler_to_overwrite].first = map;
}
} else {
maps_and_handlers.push_back(MapAndHandler(map, handler));
}

ConfigureVectorState(name, maps_and_handlers);
}

return true;
}

先计算已有的map和handler,算出number_of_valid_maps,valid的map和handler以pair的形式存放在maps_and_handlers数组中,最后调用ConfigureVectorState来更新feedback_vector

可以注意到的是该函数中有一个判断:

1
2
if (number_of_valid_maps >= FLAG_max_valid_polymorphic_map_count)
return false;

在该版本中的FLAG_max_valid_polymorphic_map_count为4,当number_of_valid_maps大于等于4的时候,该函数返回false,那么在IC::SetCache的流程会走到UpdateMegaDOMIC,但是会由于FLAG_enable_mega_dom_ic的原因,直接返回false,那么流程进入:

1
2
3
if (!is_keyed() || state() == RECOMPUTE_HANDLER) {
CopyICToMegamorphicCache(name); // <----- !is_keyed()返回true
}

IC::CopyICToMegamorphicCache

1
2
3
4
5
6
7
void IC::CopyICToMegamorphicCache(Handle<Name> name) {
std::vector<MapAndHandler> maps_and_handlers;
nexus()->ExtractMapsAndHandlers(&maps_and_handlers);
for (const MapAndHandler& map_and_handler : maps_and_handlers) {
UpdateMegamorphicCache(map_and_handler.first, name, map_and_handler.second);
}
}

提取出前面的map和handler对,然后调用UpdateMegamorphicCache进行cache的更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void IC::UpdateMegamorphicCache(Handle<Map> map, Handle<Name> name,
const MaybeObjectHandle& handler) {
if (!IsAnyHas() && !is_any_store_own()) {
stub_cache()->Set(*name, *map, *handler);
}
}
////////////////////////////////////////////////////////////////////////
// src/ic/stub-cache.cc
void StubCache::Set(Name name, Map map, MaybeObject handler) {
DCHECK(CommonStubCacheChecks(this, name, map, handler));

// Compute the primary entry.
int primary_offset = PrimaryOffset(name, map);
Entry* primary = entry(primary_, primary_offset);
MaybeObject old_handler(
TaggedValue::ToMaybeObject(isolate(), primary->value));
// If the primary entry has useful data in it, we retire it to the
// secondary cache before overwriting it.
if (old_handler != MaybeObject::FromObject(
isolate()->builtins()->code(Builtin::kIllegal)) &&
!primary->map.IsSmi()) {
Map old_map =
Map::cast(StrongTaggedValue::ToObject(isolate(), primary->map));
Name old_name =
Name::cast(StrongTaggedValue::ToObject(isolate(), primary->key));
int secondary_offset = SecondaryOffset(old_name, old_map);
Entry* secondary = entry(secondary_, secondary_offset);
*secondary = *primary;
}

// Update primary cache.
primary->key = StrongTaggedValue(name);
primary->value = TaggedValue(handler);
primary->map = StrongTaggedValue(map);
isolate()->counters()->megamorphic_stub_cache_updates()->Increment();
}

该函数结束后,接着调用ConfigureVectorState(MEGAMORPHIC, name);更新feedback vector

在调用UpdateMegamorphicCache(lookup_start_object_map(), name, handler); 把map和handler加入到cache里

测试代码3次%DebugPrint(foo)的feedback vector,对应3中ic的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// MONOMORPHIC
- slot #0 LoadProperty MONOMORPHIC {
[0]: [weak] 0x132f082c7b91 <Map(HOLEY_ELEMENTS)>
[1]: 1924
}
// POLYMORPHIC
- slot #0 LoadProperty POLYMORPHIC {
[0]: 0x132f0810ae11 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
[1]: 0x132f08005a75 <Symbol: (uninitialized_symbol)>
}
pwndbg> job 0x132f0810ae11
0x132f0810ae11: [WeakFixedArray]
- map: 0x132f08002f49 <Map>
- length: 8

0: [weak] 0x132f082c7b91 <Map(HOLEY_ELEMENTS)>
1: 1924
2: [weak] 0x132f082c7be1 <Map(HOLEY_ELEMENTS)>
3: 1668
4: [weak] 0x132f082c7c81 <Map(HOLEY_ELEMENTS)>
5: 1668
6: [weak] 0x132f082c7d21 <Map(HOLEY_ELEMENTS)>
7: 1924
// MEGAMORPHIC
- slot #0 LoadProperty MEGAMORPHIC {
[0]: 0x132f08005935 <Symbol: (megamorphic_symbol)>
[1]: 1
}

POLYMORPHIC

POLYMORPHIC状态下的LoadIC_BytecodeHandler,流程会走到:

1
2
3
4
5
6
7
8
BIND(&try_polymorphic);
{
TNode<HeapObject> strong_feedback =
GetHeapObjectIfStrong(feedback, &miss);
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call);
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback),
&if_handler, &var_handler, &miss);
}

HandlePolymorphicCase函数的逻辑大概就是循环取得feedback vector数组里保存的map和handler,和当前传入的receiver的map进行比较,如果比较成功了,就直接用handler进行处理,如果都没找到,那又回到Runtime_LoadIC_Miss

MEGAMORPHIC

MEGAMORPHIC状态下的LoadIC_BytecodeHandler,流程会走到:

1
2
3
4
5
6
7
8
9
10
11
BIND(&stub_call);
{
Comment("LoadIC_BytecodeHandler_noninlined");

// Call into the stub that implements the non-inlined parts of LoadIC.
Callable ic = Builtins::CallableFor(isolate(), Builtin::kLoadIC_Noninlined);
TNode<Code> code_target = HeapConstant(ic.code());
exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context(),
p->receiver_and_lookup_start_object(), p->name(),
p->slot(), p->vector());
}

先调用Runtime的LoadIC_Noninlined:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// src/ic/accessor-assembler.cc
void AccessorAssembler::GenerateLoadIC_Noninlined() {
using Descriptor = LoadWithVectorDescriptor;

auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto name = Parameter<Object>(Descriptor::kName);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto vector = Parameter<FeedbackVector>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);

ExitPoint direct_exit(this);
TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler), miss(this, Label::kDeferred);

TNode<MaybeObject> feedback_element = LoadFeedbackVectorSlot(vector, slot);
TNode<HeapObject> feedback = CAST(feedback_element);

LoadICParameters p(context, receiver, name, slot, vector);
TNode<Map> lookup_start_object_map = LoadReceiverMap(p.lookup_start_object());
LoadIC_Noninlined(&p, lookup_start_object_map, feedback, &var_handler,
&if_handler, &miss, &direct_exit);

BIND(&if_handler);
{
LazyLoadICParameters lazy_p(&p);
HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss,
&direct_exit);
}

BIND(&miss);
direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, context, receiver, name,
slot, vector);
}
/////////////////////////////////////////////////////////////////////////////////
void AccessorAssembler::LoadIC_Noninlined(const LoadICParameters* p,
TNode<Map> lookup_start_object_map,
TNode<HeapObject> feedback,
TVariable<MaybeObject>* var_handler,
Label* if_handler, Label* miss,
ExitPoint* exit_point) {
// Neither deprecated map nor monomorphic. These cases are handled in the
// bytecode handler.
CSA_DCHECK(this, Word32BinaryNot(IsDeprecatedMap(lookup_start_object_map)));
CSA_DCHECK(this, TaggedNotEqual(lookup_start_object_map, feedback));
CSA_DCHECK(this, Word32BinaryNot(IsWeakFixedArrayMap(LoadMap(feedback))));
DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep());

{
Label try_megamorphic(this), try_megadom(this);
GotoIf(TaggedEqual(feedback, MegamorphicSymbolConstant()),
&try_megamorphic);
GotoIf(TaggedEqual(feedback, MegaDOMSymbolConstant()), &try_megadom);
Goto(miss);

BIND(&try_megamorphic);
{
TryProbeStubCache(isolate()->load_stub_cache(), p->lookup_start_object(),
CAST(p->name()), if_handler, var_handler, miss);
}

BIND(&try_megadom);
{
TryMegaDOMCase(p->lookup_start_object(), lookup_start_object_map,
var_handler, p->vector(), p->slot(), miss, exit_point);
}
}
}

正常情况下都是走到try_megamorphic标签,调用TryProbeStubCache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// src/ic/accessor-assembler.cc
void AccessorAssembler::TryProbeStubCache(StubCache* stub_cache,
TNode<Object> lookup_start_object,
TNode<Name> name, Label* if_handler,
TVariable<MaybeObject>* var_handler,
Label* if_miss) {
Label try_secondary(this), miss(this);

Counters* counters = isolate()->counters();
IncrementCounter(counters->megamorphic_stub_cache_probes(), 1);

// Check that the {lookup_start_object} isn't a smi.
GotoIf(TaggedIsSmi(lookup_start_object), &miss);

TNode<Map> lookup_start_object_map = LoadMap(CAST(lookup_start_object));

// Probe the primary table.
TNode<IntPtrT> primary_offset =
StubCachePrimaryOffset(name, lookup_start_object_map);
TryProbeStubCacheTable(stub_cache, kPrimary, primary_offset, name,
lookup_start_object_map, if_handler, var_handler,
&try_secondary);

BIND(&try_secondary);
{
// Probe the secondary table.
TNode<IntPtrT> secondary_offset =
StubCacheSecondaryOffset(name, lookup_start_object_map);
TryProbeStubCacheTable(stub_cache, kSecondary, secondary_offset, name,
lookup_start_object_map, if_handler, var_handler,
&miss);
}

BIND(&miss);
{
IncrementCounter(counters->megamorphic_stub_cache_misses(), 1);
Goto(if_miss);
}
}
/////////////////////////////////////////////////////////////////////////////////
void AccessorAssembler::TryProbeStubCacheTable(
StubCache* stub_cache, StubCacheTable table_id, TNode<IntPtrT> entry_offset,
TNode<Object> name, TNode<Map> map, Label* if_handler,
TVariable<MaybeObject>* var_handler, Label* if_miss) {
StubCache::Table table = static_cast<StubCache::Table>(table_id);
// The {table_offset} holds the entry offset times four (due to masking
// and shifting optimizations).
const int kMultiplier =
sizeof(StubCache::Entry) >> StubCache::kCacheIndexShift;
entry_offset = IntPtrMul(entry_offset, IntPtrConstant(kMultiplier));

TNode<ExternalReference> key_base = ExternalConstant(
ExternalReference::Create(stub_cache->key_reference(table)));

// Check that the key in the entry matches the name.
DCHECK_EQ(0, offsetof(StubCache::Entry, key));
TNode<HeapObject> cached_key =
CAST(Load(MachineType::TaggedPointer(), key_base, entry_offset));
GotoIf(TaggedNotEqual(name, cached_key), if_miss);

// Check that the map in the entry matches.
TNode<Object> cached_map = Load<Object>(
key_base,
IntPtrAdd(entry_offset, IntPtrConstant(offsetof(StubCache::Entry, map))));
GotoIf(TaggedNotEqual(map, cached_map), if_miss);

TNode<MaybeObject> handler = ReinterpretCast<MaybeObject>(
Load(MachineType::AnyTagged(), key_base,
IntPtrAdd(entry_offset,
IntPtrConstant(offsetof(StubCache::Entry, value)))));

// We found the handler.
*var_handler = handler;
Goto(if_handler);
}

在stub_cache中寻找是否已经有盖name的cache了,如果有,那就比较下map是否也相等,如果相等的话,那就用cache里该name的handler,如果不想等或者cache中没有该name的话就又回到runtime的LoadIC_Miss

这个stub_cache是在isolate实例上的,可以通过p &((v8::internal::Isolate*)isolate)->load_stub_cache_找到,这说明MEGAMORPHIC 状态下的handler其实是共享的,只要name和map相等,那就能共享handler(不知道有没有特殊情况,通常情况下是共享的)

那么super属性的获取又是如何的呢

super property

js class的继承,那个this的指向和其它语言(c++/java)有点不同,很神奇;

测试例子:

1
2
3
4
5
6
7
8
9
10
11
class B {
m() {
return super.prop;
}
}

var a = {prop : 1.1}
B.prototype.__proto__ = a;

var b = new B();
console.log(b.m());

查看classB的m函数的字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[generated bytecode for function: m (0x1139082934c1 <SharedFunctionInfo m>)]
Bytecode length: 7
Parameter count 1
Register count 0
Frame size 0
OSR nesting level: 0
Bytecode Age: 0
0x1139082937fe @ 0 : 17 02 LdaImmutableCurrentContextSlot [2]
0x113908293800 @ 2 : 2e 02 00 00 LdaNamedPropertyFromSuper <this>, [0], [0]
0x113908293804 @ 6 : a9 Return
Constant pool (size = 1)
0x1139082937d1: [FixedArray] in OldSpace
- map: 0x113908002209 <Map>
- length: 1
0: 0x1139082933e5 <String[4]: #prop>

可以看到用的是LdaNamedPropertyFromSuper字节码,其处理程序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/interpreter/interpreter-generator.cc
IGNITION_HANDLER(LdaNamedPropertyFromSuper, InterpreterAssembler) {
TNode<Object> receiver = LoadRegisterAtOperandIndex(0);
TNode<HeapObject> home_object = CAST(GetAccumulator());
TNode<Object> home_object_prototype = LoadMapPrototype(LoadMap(home_object));
TNode<Object> name = LoadConstantPoolEntryAtOperandIndex(1);
TNode<TaggedIndex> slot = BytecodeOperandIdxTaggedIndex(2);
TNode<HeapObject> feedback_vector = LoadFeedbackVector();
TNode<Context> context = GetContext();

TNode<Object> result =
CallBuiltin(Builtin::kLoadSuperIC, context, receiver,
home_object_prototype, name, slot, feedback_vector);
SetAccumulator(result);
Dispatch();
}
/////////////////////////////////////////////////////////////////////////
void AccessorAssembler::GenerateLoadSuperIC() {
using Descriptor = LoadWithReceiverAndVectorDescriptor;

auto receiver = Parameter<Object>(Descriptor::kReceiver);
auto lookup_start_object = Parameter<Object>(Descriptor::kLookupStartObject);
auto name = Parameter<Object>(Descriptor::kName);
auto slot = Parameter<TaggedIndex>(Descriptor::kSlot);
auto vector = Parameter<HeapObject>(Descriptor::kVector);
auto context = Parameter<Context>(Descriptor::kContext);

LoadICParameters p(context, receiver, name, slot, vector,
lookup_start_object);
LoadSuperIC(&p);
}

流程交由Builtin::kLoadSuperIC处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void AccessorAssembler::LoadSuperIC(const LoadICParameters* p) {
ExitPoint direct_exit(this);

TVARIABLE(MaybeObject, var_handler);
Label if_handler(this, &var_handler), no_feedback(this),
non_inlined(this, Label::kDeferred), try_polymorphic(this),
miss(this, Label::kDeferred);

GotoIf(IsUndefined(p->vector()), &no_feedback);

// The lookup start object cannot be a SMI, since it's the home object's
// prototype, and it's not possible to set SMIs as prototypes.
TNode<Map> lookup_start_object_map =
LoadReceiverMap(p->lookup_start_object());
GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss);

TNode<MaybeObject> feedback =
TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map,
&if_handler, &var_handler, &try_polymorphic);

BIND(&if_handler);
{
LazyLoadICParameters lazy_p(p);
HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss,
&direct_exit);
}

BIND(&no_feedback);
{ LoadSuperIC_NoFeedback(p); }

BIND(&try_polymorphic);
TNode<HeapObject> strong_feedback = GetHeapObjectIfStrong(feedback, &miss);
{
Comment("LoadSuperIC_try_polymorphic");
GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined);
HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback),
&if_handler, &var_handler, &miss);
}

BIND(&non_inlined);
{
// LoadIC_Noninlined can be used here, since it handles the
// lookup_start_object != receiver case gracefully.
LoadIC_Noninlined(p, lookup_start_object_map, strong_feedback, &var_handler,
&if_handler, &miss, &direct_exit);
}

BIND(&miss);
direct_exit.ReturnCallRuntime(Runtime::kLoadWithReceiverIC_Miss, p->context(),
p->receiver(), p->lookup_start_object(),
p->name(), p->slot(), p->vector());
}

和 LoadIC_BytecodeHandler几乎一模一样,不同的就是lookup_start_object,从IGNITION_HANDLER(LdaNamedPropertyFromSuper, InterpreterAssembler)和AccessorAssembler::GenerateLoadSuperIC可以看出,lookup_start_object来自home_object_prototype,而home_object_prototype = LoadMapPrototype(LoadMap(home_object));,这个home_object指向的应该是B,home_object_prototype 就是a

剩下的流程好像都差不多

后记

大概就是这么粗浅的调试和看了下源码,对inline cache有一点认识

扫一扫,分享到微信

微信分享二维码
FuzzILLI之FuzzIL
  1. 1. 前言
  2. 2. 环境
  3. 3. 第1次调用
    1. 3.1. CompileLazy
    2. 3.2. runtime complielazy
    3. 3.3. Compiler::Compile
    4. 3.4. initialize feedbackCell
    5. 3.5. InterpreterEntryTrampoline
    6. 3.6. LdaNamedProperty
    7. 3.7. Return
  4. 4. 第10次调用
    1. 4.1. LoadIC_BytecodeHandler
    2. 4.2. Runtime_LoadIC_Miss
    3. 4.3. ic.UpdateState
    4. 4.4. LoadIC::Load
    5. 4.5. ComputeHandler
  5. 5. 第11次调用
    1. 5.1. AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase
  6. 6. MONOMORPHIC && POLYMORPHIC && MEGAMORPHIC
    1. 6.1. IC::CopyICToMegamorphicCache
    2. 6.2. POLYMORPHIC
    3. 6.3. MEGAMORPHIC
  7. 7. super property
  8. 8. 后记
© 2022 ruan
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 好舍友
beginner