v8 pipeline

总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v8::internal::GeneratedCode
-> RUNTIME_FUNCTION(Runtime_CompileOptimized_NotConcurrent)
-> Compiler::CompileOptimized
-> GetOptimizedCode
-> GetOptimizedCodeNow
-> OptimizedCompilationJob::PrepareJob
-> PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl
-> PipelineImpl::CreateGraph()
-> BytecodeGraphBuilder::CreateGraph()
...
-> SetStart
-> NewNodeUnchecked
-> Node::New
...
-> env
-> VisitBytecodes

Compiler::CompileOptimized(function, ConcurrencyMode::kNotConcurrent)

参数1是要compile的function,参数2是一个标志,应该是和线程相关,表示function不在“正在编译的函数的队列”里。

1
2
3
4
5
6
7
8
9
10
11
if (mode == ConcurrencyMode::kConcurrent) {
if (GetOptimizedCodeLater(job.get(), isolate)) {
job.release(); // The background recompile job owns this now.

// Set the optimization marker and return a code object which checks it.
function->SetOptimizationMarker(OptimizationMarker::kInOptimizationQueue);
DCHECK(function->IsInterpreted() ||
(!function->is_compiled() && function->shared()->IsInterpreted()));
DCHECK(function->shared()->HasBytecodeArray());
return BUILTIN_CODE(isolate, InterpreterEntryTrampoline);
}

在这个函数中进行编译,这个函数首先检查function是否已经被编译过了。

1
if (function->IsOptimized()) return true;

然后进行编译优化,如果编译优化成功则以后在js中调用函数都执行编译后的code

1
2
3
4
if (!GetOptimizedCode(function, mode).ToHandle(&code)) {
...
// Install code on closure.
function->set_code(*code);

如果失败,则回到解释帧InterpreterEntryTrampoline执行

1
code = BUILTIN_CODE(isolate, InterpreterEntryTrampoline);

bool GetOptimizedCodeNow(OptimizedCompilationJob* job, Isolate* isolate)

1
2
3
if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED ||
job->ExecuteJob() != CompilationJob::SUCCEEDED ||
job->FinalizeJob(isolate) != CompilationJob::SUCCEEDED) {

CompilationJob::Status OptimizedCompilationJob::PrepareJob

v8里有非常多的status,很有意思。

1
2
3
4
5
6
7
8
9
10
DEFINE_BOOL(trace_opt, false, "trace lazy optimization")
...
...
if (FLAG_trace_opt && compilation_info()->IsOptimizing()) {
OFStream os(stdout);
os << "[compiling method " << Brief(*compilation_info()->closure())
<< " using " << compiler_name_;
if (compilation_info()->is_osr()) os << " OSR";
os << "]" << std::endl;
}

这个函数就是调用

1
return UpdateState(PrepareJobImpl(isolate), State::kReadyToExecute);

PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl

前面根据一些标志位进行设置,包括下面这些等。

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
// OptimizedCompilationInfo encapsulates the information needed to compile
// optimized code for a given function, and the results of the optimized
// compilation.
class V8_EXPORT_PRIVATE OptimizedCompilationInfo final {
public:
// Various configuration flags for a compilation, as well as some properties
// of the compiled code produced by a compilation.
enum Flag {
kAccessorInliningEnabled = 1 << 0,
kFunctionContextSpecializing = 1 << 1,
kInliningEnabled = 1 << 2,
kDisableFutureOptimization = 1 << 3,
kSplittingEnabled = 1 << 4,
kSourcePositionsEnabled = 1 << 5,
kBailoutOnUninitialized = 1 << 6,
kLoopPeelingEnabled = 1 << 7,
kUntrustedCodeMitigations = 1 << 8,
kSwitchJumpTableEnabled = 1 << 9,
kCalledWithCodeStartRegister = 1 << 10,
kPoisonRegisterArguments = 1 << 11,
kAllocationFoldingEnabled = 1 << 12,
kAnalyzeEnvironmentLiveness = 1 << 13,
kTraceTurboJson = 1 << 14,
kTraceTurboGraph = 1 << 15,
kTraceTurboScheduled = 1 << 16,
};
...
...
...
FLAG_always_opt
FLAG_turbo_loop_peeling
FLAG_turbo_inlining
FLAG_inline_accessors
FLAG_turbo_allocation_folding

然后开始创建Graph

1
2
3
4
if (!pipeline_.CreateGraph()) {
if (isolate->has_pending_exception()) return FAILED; // Stack overflowed.
return AbortOptimization(BailoutReason::kGraphBuildingFailed);
}

PipelineImpl::CreateGraph()

检查trace标志位并做相应操作,主要是做记录。

1
2
if (info()->trace_turbo_json_enabled() ||
info()->trace_turbo_graph_enabled()) {

通过添加修饰器来记录源码位置。

1
data->source_positions()->AddDecorator();

然后

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
  Run<GraphBuilderPhase>();
RunPrintAndVerify("Initial untyped", true);

// Perform function context specialization and inlining (if enabled).
Run<InliningPhase>();
RunPrintAndVerify("Inlined", true);

// Remove dead->live edges from the graph.
Run<EarlyGraphTrimmingPhase>();
RunPrintAndVerify("Early trimmed", true);

// Run the type-sensitive lowerings and optimizations on the graph.
{
// Determine the Typer operation flags.
Typer::Flags flags = Typer::kNoFlags;
if (is_sloppy(info()->shared_info()->language_mode()) &&
info()->shared_info()->IsUserJavaScript()) {
// Sloppy mode functions always have an Object for this.
flags |= Typer::kThisIsReceiver;
}
if (IsClassConstructor(info()->shared_info()->kind())) {
// Class constructors cannot be [[Call]]ed.
flags |= Typer::kNewTargetIsReceiver;
}

// Type the graph and keep the Typer running on newly created nodes within
// this scope; the Typer is automatically unlinked from the Graph once we
// leave this scope below.
Typer typer(isolate(), flags, data->graph());
Run<TyperPhase>(&typer);
RunPrintAndVerify("Typed");

// Lower JSOperators where we can determine types.
Run<TypedLoweringPhase>();
RunPrintAndVerify("Lowered typed");
}

// Do some hacky things to prepare for the optimization phase.
// (caching handles, etc.).
Run<ConcurrentOptimizationPrepPhase>();

data->EndPhaseKind();

return true;
}

BytecodeGraphBuilder::CreateGraph()

设置图的基本结构。
{Start}的输出是形参(包括receiver)加上new target, arguments数目,context和closure

1
2
int actual_parameter_count = bytecode_array()->parameter_count() + 4;
graph()->SetStart(graph()->NewNode(common()->Start(actual_parameter_count)));

NewNode是用于创建新node的帮助函数。
common()返回CommonOperatorBuilder*的common_,差不多是一个op的集合了,然后从中选择Start

1
2
3
4
5
6
7
8
9
10
  CommonOperatorBuilder* common() const { return common_; }
...
const Operator* Start(int value_output_count);
...
const Operator* CommonOperatorBuilder::Start(int value_output_count) {
return new (zone()) Operator( // --
IrOpcode::kStart, Operator::kFoldable | Operator::kNoThrow, // opcode
"Start", // name
0, 0, 0, value_output_count, 1, 1); // counts
}

这个文件还是比较有用的,common-operator.cc,因为NewNode的opcode参数从这里初始化。
回到NewNode看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  // Factory template for nodes with static input counts.
template <typename... Nodes>
Node* NewNode(const Operator* op, Nodes*... nodes) {
std::array<Node*, sizeof...(nodes)> nodes_arr{{nodes...}};
return NewNode(op, nodes_arr.size(), nodes_arr.data());
}
...
Node* Graph::NewNode(const Operator* op, int input_count, Node* const* inputs,
bool incomplete) {
Node* node = NewNodeUnchecked(op, input_count, inputs, incomplete);
Verifier::VerifyNode(node);
return node;
}
...
Node* Graph::NewNodeUnchecked(const Operator* op, int input_count,
Node* const* inputs, bool incomplete) {
Node* const node =
Node::New(zone(), NextNodeId(), op, input_count, inputs, incomplete);
Decorate(node);
return node;
}

最后是执行到了这里,于是我们来分析一下这个函数。

Node::New

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
Node* Node::New(Zone* zone, NodeId id, const Operator* op, int input_count,
Node* const* inputs, bool has_extensible_inputs) {
Node** input_ptr;
Use* use_ptr;
Node* node;
bool is_inline;

if (input_count > kMaxInlineCapacity) {
// Allocate out-of-line inputs.
int capacity =
has_extensible_inputs ? input_count + kMaxInlineCapacity : input_count;
OutOfLineInputs* outline = OutOfLineInputs::New(zone, capacity);

// Allocate node.
void* node_buffer = zone->New(sizeof(Node));
node = new (node_buffer) Node(id, op, kOutlineMarker, 0);
node->inputs_.outline_ = outline;

outline->node_ = node;
outline->count_ = input_count;

input_ptr = outline->inputs_;
use_ptr = reinterpret_cast<Use*>(outline);
is_inline = false;
} else {
// Allocate node with inline inputs.
int capacity = input_count;
if (has_extensible_inputs) {
const int max = kMaxInlineCapacity;
capacity = std::min(input_count + 3, max);
}

size_t size = sizeof(Node) + capacity * (sizeof(Node*) + sizeof(Use));
intptr_t raw_buffer = reinterpret_cast<intptr_t>(zone->New(size));
void* node_buffer =
reinterpret_cast<void*>(raw_buffer + capacity * sizeof(Use));

node = new (node_buffer) Node(id, op, input_count, capacity);
input_ptr = node->inputs_.inline_;
use_ptr = reinterpret_cast<Use*>(node);
is_inline = true;
}

// Initialize the input pointers and the uses.
for (int current = 0; current < input_count; ++current) {
Node* to = *inputs++;
input_ptr[current] = to;
Use* use = use_ptr - 1 - current;
use->bit_field_ = Use::InputIndexField::encode(current) |
Use::InlineField::encode(is_inline);
to->AppendUse(use);
}
node->Verify();
return node;
}

首先定义了几个局部变量

1
2
3
4
Node** input_ptr;
Use* use_ptr;
Node* node;
bool is_inline;

然后判断input_count是否大于kMaxInineCapacity
注意这里的input_count来自这里的nodes_arr.size(),此处对于start的情况,NewNode(common()->Start(actual_parameter_count)));,可以看出这个结果是0。
这是个比较特殊的情况,后面我们再分析几个node的生成看一下这个逻辑。

1
2
3
4
Node* NewNode(const Operator* op, Nodes*... nodes) {
std::array<Node*, sizeof...(nodes)> nodes_arr{{nodes...}};
return NewNode(op, nodes_arr.size(), nodes_arr.data());
}

为什么这里有这样的一个比较呢?是因为v8对node的存储决定的
从注释里可以找到

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

//============================================================================
//== Memory layout ===========================================================
//============================================================================
// Saving space for big graphs is important. We use a memory layout trick to
// be able to map {Node} objects to {Use} objects and vice-versa in a
// space-efficient manner.
//
// {Use} links are laid out in memory directly before a {Node}, followed by
// direct pointers to input {Nodes}.
//
// inline case:
// |Use #N |Use #N-1|...|Use #1 |Use #0 |Node xxxx |I#0|I#1|...|I#N-1|I#N|
// ^ ^ ^
// + Use + Node + Input
//
// Since every {Use} instance records its {input_index}, pointer arithmetic
// can compute the {Node}.
//
// out-of-line case:
// |Node xxxx |
// ^ + outline ------------------+
// +----------------------------------------+
// | |
// v | node
// |Use #N |Use #N-1|...|Use #1 |Use #0 |OOL xxxxx |I#0|I#1|...|I#N-1|I#N|
// ^ ^
// + Use + Input
//
// Out-of-line storage of input lists is needed if appending an input to
// a node exceeds the maximum inline capacity.

如果是小于kMaxInineCapacity,则可以直接将inputs内联在node中。
这里的计算方法是,首先计算capacity,默认应该是等于input_count,如果有has_extensible_inputs,则在input_count + 3和kMaxInlineCapacity选取一个最小值。
这个has_extensible_inputs我还不是很懂,后面看看吧

1
2
3
4
5
int capacity = input_count;
if (has_extensible_inputs) {
const int max = kMaxInlineCapacity;
capacity = std::min(input_count + 3, max);
}

然后计算size大小,并为node和它的use/input分配内存。

1
2
3
4
size_t size = sizeof(Node) + capacity * (sizeof(Node*) + sizeof(Use));
intptr_t raw_buffer = reinterpret_cast<intptr_t>(zone->New(size));
void* node_buffer =
reinterpret_cast<void*>(raw_buffer + capacity * sizeof(Use));

顺便说一下,一个Use大小是24字节,一个Node是40字节

计算好size之后进入这个函数,在这生成新的node。

1
2
3
4
5
6
7
8
9
10
Node::Node(NodeId id, const Operator* op, int inline_count, int inline_capacity)
: op_(op),
mark_(0),
bit_field_(IdField::encode(id) | InlineCountField::encode(inline_count) |
InlineCapacityField::encode(inline_capacity)),
first_use_(nullptr) {
// Inputs must either be out of line or within the inline capacity.
DCHECK_GE(kMaxInlineCapacity, inline_capacity);
DCHECK(inline_count == kOutlineMarker || inline_count <= inline_capacity);
}

最后需要为这个node建立input/use关系,这里的逻辑就是,首先根据当前node的input_count数。
依次设置to为input节点。

1
2
for (int current = 0; current < input_count; ++current) {
Node* to = *inputs++;

然后由于input_ptr指向node的inputs区域,在node的inputs区域记录它的input节点的地址。

1
2
3
input_ptr = node->inputs_.inline_;
...
input_ptr[current] = to;

通过这种方式就将节点的input关系建立好了。
然后需要考虑一下use关系,现在我们可以看到use_ptr指向的是当前node的地址。
通过use_ptr和{input_index}来计算出use,然后在use里记录当前{input_index}的值,于是我们可以通过这个值来做简单的算数计算来找到node。

1
2
3
Use* use = use_ptr - 1 - current;
use->bit_field_ = Use::InputIndexField::encode(current) |
Use::InlineField::encode(is_inline);

然后

1
2
3
4
5
6
7
8
9
10
11
to->AppendUse(use);
...
...
void Node::AppendUse(Use* use) {
DCHECK(first_use_ == nullptr || first_use_->prev == nullptr);
DCHECK_EQ(this, *use->input_ptr());
use->next = first_use_;
use->prev = nullptr;
if (first_use_) first_use_->prev = use;
first_use_ = use;
}

于是从当前节点的input节点到当前节点,这样的一个{input}->{node}的use关系就建立起来了。
注意first_use是Node结构的一个成员变量。

或许这么说还是有点难懂,其实就是假设有一个节点A,它有0,1,2,3这么几个input节点,0,1,2,3代表的也是input_index。
然后对于每一个它的input节点,都要从它的Use部分取一个分配给它的input,如图。

然后因为分配出去的Use里面有记录这个input节点对应的input index,于是很容易就可以计算出来Node的地址。
这样,一个{input}->{node}的{Use #index}的关系就建立好了,而且很容易就可以通过#index来进行算数运算,得到真正的{input}->{node},这样的use关系。

之所以需要这么麻烦,可能也是为了让graph IR有SSA的性质……

Node::New结束之后,此时Start节点已经被构建好了,请记住Node::New做的事情,因为后面建立新的node也是通过这个函数来完成的。

BytecodeGraphBuilder::Environment

回到BytecodeGraphBuilder::CreateGraph()来看一下,在Start创建之后,初始化env并切换到它。
在看env的初始化之前,先看一个重要的class

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
// The abstract execution environment simulates the content of the interpreter
// register file. The environment performs SSA-renaming of all tracked nodes at
// split and merge points in the control flow.
class BytecodeGraphBuilder::Environment : public ZoneObject {
public:
Environment(BytecodeGraphBuilder* builder, int register_count,
int parameter_count,
interpreter::Register incoming_new_target_or_generator,
Node* control_dependency);

// Specifies whether environment binding methods should attach frame state
// inputs to nodes representing the value being bound. This is done because
// the {OutputFrameStateCombine} is closely related to the binding method.
enum FrameStateAttachmentMode { kAttachFrameState, kDontAttachFrameState };

int parameter_count() const { return parameter_count_; }
int register_count() const { return register_count_; }

Node* LookupAccumulator() const;
Node* LookupRegister(interpreter::Register the_register) const;
Node* LookupGeneratorState() const;

void BindAccumulator(Node* node,
FrameStateAttachmentMode mode = kDontAttachFrameState);
void BindRegister(interpreter::Register the_register, Node* node,
FrameStateAttachmentMode mode = kDontAttachFrameState);
void BindRegistersToProjections(
interpreter::Register first_reg, Node* node,
FrameStateAttachmentMode mode = kDontAttachFrameState);
void BindGeneratorState(Node* node);
void RecordAfterState(Node* node,
FrameStateAttachmentMode mode = kDontAttachFrameState);

// Effect dependency tracked by this environment.
Node* GetEffectDependency() { return effect_dependency_; }
void UpdateEffectDependency(Node* dependency) {
effect_dependency_ = dependency;
}

// Preserve a checkpoint of the environment for the IR graph. Any
// further mutation of the environment will not affect checkpoints.
Node* Checkpoint(BailoutId bytecode_offset, OutputFrameStateCombine combine,
const BytecodeLivenessState* liveness);

// Control dependency tracked by this environment.
Node* GetControlDependency() const { return control_dependency_; }
void UpdateControlDependency(Node* dependency) {
control_dependency_ = dependency;
}

Node* Context() const { return context_; }
void SetContext(Node* new_context) { context_ = new_context; }

Environment* Copy();
void Merge(Environment* other, const BytecodeLivenessState* liveness);

void FillWithOsrValues();
void PrepareForLoop(const BytecodeLoopAssignments& assignments,
const BytecodeLivenessState* liveness);
void PrepareForLoopExit(Node* loop,
const BytecodeLoopAssignments& assignments,
const BytecodeLivenessState* liveness);

private:
explicit Environment(const Environment* copy);

bool StateValuesRequireUpdate(Node** state_values, Node** values, int count);
void UpdateStateValues(Node** state_values, Node** values, int count);
Node* GetStateValuesFromCache(Node** values, int count,
const BitVector* liveness, int liveness_offset);

int RegisterToValuesIndex(interpreter::Register the_register) const;

Zone* zone() const { return builder_->local_zone(); }
Graph* graph() const { return builder_->graph(); }
CommonOperatorBuilder* common() const { return builder_->common(); }
BytecodeGraphBuilder* builder() const { return builder_; }
const NodeVector* values() const { return &values_; }
NodeVector* values() { return &values_; }
int register_base() const { return register_base_; }
int accumulator_base() const { return accumulator_base_; }

BytecodeGraphBuilder* builder_;
int register_count_;
int parameter_count_;
Node* context_;
Node* control_dependency_;
Node* effect_dependency_;
NodeVector values_;
Node* parameters_state_values_;
Node* generator_state_;
int register_base_;
int accumulator_base_;
};

从上面可知,values()返回一个NodeVector values_。
然后继续看env的初始化

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
  Environment env(this, bytecode_array()->register_count(),
bytecode_array()->parameter_count(),
bytecode_array()->incoming_new_target_or_generator_register(),
graph()->start());
set_environment(&env);
...
...
...
BytecodeGraphBuilder::Environment::Environment(
BytecodeGraphBuilder* builder, int register_count, int parameter_count,
interpreter::Register incoming_new_target_or_generator,
Node* control_dependency)
: builder_(builder),
register_count_(register_count),
parameter_count_(parameter_count),
control_dependency_(control_dependency),
effect_dependency_(control_dependency),
values_(builder->local_zone()),
parameters_state_values_(nullptr),
generator_state_(nullptr) {
// The layout of values_ is:
//
// [receiver] [parameters] [registers] [accumulator]
//
// parameter[0] is the receiver (this), parameters 1..N are the
// parameters supplied to the method (arg0..argN-1). The accumulator
// is stored separately.
// Parameters including the receiver
....
}

注意这句话

1
2
parameter[0] is the receiver (this), parameters 1..N are the
Parameters including the receiver

首先创建parameter节点,Start作为parameter的input节点。

1
2
3
4
5
6
for (int i = 0; i < parameter_count; i++) {
...
const Operator* op = common()->Parameter(i, debug_name);
Node* parameter = builder->graph()->NewNode(op, graph()->start());
values()->push_back(parameter);
}

然后向values_这个NodeVector的end之前,插入register_count个值为undefined_constant的Node节点。

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
// Registers
register_base_ = static_cast<int>(values()->size());
Node* undefined_constant = builder->jsgraph()->UndefinedConstant();
values()->insert(values()->end(), register_count, undefined_constant);
...
...
DEFINE_GETTER(UndefinedConstant, HeapConstant(factory()->undefined_value()))
...
...
// value v8::internal::Handle<v8::internal::HeapObject>
Node* JSGraph::HeapConstant(Handle<HeapObject> value) {
Node** loc = cache_.FindHeapConstant(value);
if (*loc == nullptr) {
*loc = graph()->NewNode(common()->HeapConstant(value));
}
return *loc;
}
...
...
const Operator* CommonOperatorBuilder::HeapConstant(
const Handle<HeapObject>& value) {
return new (zone()) Operator1<Handle<HeapObject>>( // --
IrOpcode::kHeapConstant, Operator::kPure, // opcode
"HeapConstant", // name
0, 0, 0, 1, 0, 0, // counts
value); // parameter
}

先从cache中检查是否已经有HeapConstant,如果没有就新建再返回,如果有就直接返回cache里的。

然后再向value_的最后插入一个undefined_constant节点作为Accumulator。

1
2
3
// Accumulator
accumulator_base_ = static_cast<int>(values()->size());
values()->push_back(undefined_constant);

然后设置Context

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
  // Context
int context_index = Linkage::GetJSCallContextParamIndex(parameter_count);
const Operator* op = common()->Parameter(context_index, "%context");
context_ = builder->graph()->NewNode(op, graph()->start());
...
...
...
// A special {Parameter} index for JSCalls that represents the context.
static int GetJSCallContextParamIndex(int parameter_count) {
return parameter_count + 2; // Parameter (arity + 2) is special.
}
...
...
const Operator* CommonOperatorBuilder::Parameter(int index,
const char* debug_name) {
if (!debug_name) {
switch (index) {
#define CACHED_PARAMETER(index) \
case index: \
return &cache_.kParameter##index##Operator;
CACHED_PARAMETER_LIST(CACHED_PARAMETER)
#undef CACHED_PARAMETER
default:
break;
}
}
// Uncached.
return new (zone()) Operator1<ParameterInfo>( // --
IrOpcode::kParameter, Operator::kPure, // opcode
"Parameter", // name
1, 0, 0, 1, 0, 0, // counts
ParameterInfo(index, debug_name)); // parameter info
}

context也创建了一个parameter的node,用来做什么的我还没看懂,可能需要好好看看log或者compiler/linkage.h这个文件

VisitBytecodes

V8准备一个称为v8::internal::AstVisitor的基类,简称AstVisitor,从AST生成bytecode。
AstVisitor是一个使用Vistor模式的类。
在深度优先搜索AST时调用相应的回调函数。
生成的bytecode存放在bytecode数组当中,用Javascript来模拟这个结构,看起来像这样。


当然这个并不重要,回顾一下而已。

VisitBytecodes首先进行bytecode_analysis,在这里面进行包括liveness分析等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BytecodeAnalysis bytecode_analysis(bytecode_array(), local_zone(),
analyze_environment_liveness());
bytecode_analysis.Analyze(osr_offset_);
set_bytecode_analysis(&bytecode_analysis);

interpreter::BytecodeArrayIterator iterator(bytecode_array());
set_bytecode_iterator(&iterator);
SourcePositionTableIterator source_position_iterator(
handle(bytecode_array()->SourcePositionTable()));

if (analyze_environment_liveness() && FLAG_trace_environment_liveness) {
OFStream of(stdout);

bytecode_analysis.PrintLivenessTo(of);
}

如果想观察liveness过程,可以启用这个flag
DEFINE_BOOL(trace_environment_liveness, false, "trace liveness of local variable slots")

bytecode_array被设置迭代,然后通过VisitSingleBytecode一个个处理。

1
2
3
for (; !iterator.done(); iterator.Advance()) {
VisitSingleBytecode(&source_position_iterator);
}

这个函数前面就是一些获取bytecode并偏移寻找下一个还有一些其他判断,主要的内容其实是这个大的switch case,对不同bytecode进行不同处理。

1
2
3
4
5
6
7
8
9
10
    switch (iterator.current_bytecode()) {
#define BYTECODE_CASE(name, ...) \
case interpreter::Bytecode::k##name: \
Visit##name(); \
break;
BYTECODE_LIST(BYTECODE_CASE)
#undef BYTECODE_CODE
}
}
}

BYTECODE_LIST在bytecode.h里,太长了就不列了。

VisitSingleBytecode里有很多分支,我捡一些写一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[generated bytecode for function: foo]
Parameter count 2
Frame size 24
80 E> 0x186d973a4f8a @ 0 : a0 StackCheck
97 S> 0x186d973a4f8b @ 1 : 28 02 00 00 LdaNamedProperty a0, [0], [0]
0x186d973a4f8f @ 5 : 26 fb Star r0
0x186d973a4f91 @ 7 : 0c 64 LdaSmi [100]
0x186d973a4f93 @ 9 : 26 f9 Star r2
97 E> 0x186d973a4f95 @ 11 : 57 fb 02 f9 02 CallProperty1 r0, a0, r2, [2]
110 S> 0x186d973a4f9a @ 16 : a4 Return
Constant pool (size = 1)
0x186d973a4f19: [FixedArray] in OldSpace
- map: 0x186d90c023c1 <Map>
- length: 1
0: 0x186dc2812029 <String[7]: indexOf>
Handler Table (size = 0)
  • VisitStackCheck
    在为StackCheck构建node之前,如果没有一个能够支配(effect-dom)它的checkpoint节点,那么会先创建一个精确的checkpoint节点。
    于是在PrepareEagerCheckpoint调用Node* node = NewNode(common()->Checkpoint());
    即使我们在某种情况下跳过了checkpoint的创建,依然会染着Effect边为StackCheck寻找一个能够支配(effect-dom)它的Checkpoint。
    1
    V(Checkpoint, Operator::kKontrol, 0, 1, 1, 0, 1, 0)
    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
    void BytecodeGraphBuilder::VisitStackCheck() {
    PrepareEagerCheckpoint();
    Node* node = NewNode(javascript()->StackCheck());
    environment()->RecordAfterState(node, Environment::kAttachFrameState);
    }
    ...
    ...
    void BytecodeGraphBuilder::PrepareEagerCheckpoint() {
    if (needs_eager_checkpoint()) {
    // Create an explicit checkpoint node for before the operation. This only
    // needs to happen if we aren't effect-dominated by a {Checkpoint} already.
    mark_as_needing_eager_checkpoint(false);
    Node* node = NewNode(common()->Checkpoint());
    DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
    DCHECK_EQ(IrOpcode::kDead,
    NodeProperties::GetFrameStateInput(node)->opcode());
    BailoutId bailout_id(bytecode_iterator().current_offset());

    const BytecodeLivenessState* liveness_before =
    bytecode_analysis()->GetInLivenessFor(
    bytecode_iterator().current_offset());

    Node* frame_state_before = environment()->Checkpoint(
    bailout_id, OutputFrameStateCombine::Ignore(), liveness_before);
    NodeProperties::ReplaceFrameStateInput(node, frame_state_before);
    #ifdef DEBUG
    } else {
    // In case we skipped checkpoint creation above, we must be able to find an
    // existing checkpoint that effect-dominates the nodes about to be created.
    // Starting a search from the current effect-dependency has to succeed.
    Node* effect = environment()->GetEffectDependency();
    while (effect->opcode() != IrOpcode::kCheckpoint) {
    DCHECK(effect->op()->HasProperty(Operator::kNoWrite));
    DCHECK_EQ(1, effect->op()->EffectInputCount());
    effect = NodeProperties::GetEffectInput(effect);
    }
    }
    #else
    }
  • VisitLdaNamedProperty
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const Operator* JSOperatorBuilder::LoadNamed(Handle<Name> name,
    const VectorSlotPair& feedback) {
    NamedAccess access(LanguageMode::kSloppy, name, feedback);
    return new (zone()) Operator1<NamedAccess>( // --
    IrOpcode::kJSLoadNamed, Operator::kNoProperties, // opcode
    "JSLoadNamed", // name
    1, 1, 1, 1, 1, 2, // counts
    access); // parameter
    }
  • VisitStar
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
void BytecodeGraphBuilder::BuildCall(ConvertReceiverMode receiver_mode,
Node* const* args, size_t arg_count,
int slot_id) {
DCHECK_EQ(interpreter::Bytecodes::GetReceiverMode(
bytecode_iterator().current_bytecode()),
receiver_mode);
PrepareEagerCheckpoint();

VectorSlotPair feedback = CreateVectorSlotPair(slot_id);

CallFrequency frequency = ComputeCallFrequency(slot_id);
const Operator* op =
javascript()->Call(arg_count, frequency, feedback, receiver_mode,
GetSpeculationMode(slot_id));
JSTypeHintLowering::LoweringResult lowering = TryBuildSimplifiedCall(
op, args, static_cast<int>(arg_count), feedback.slot());
if (lowering.IsExit()) return;

Node* node = nullptr;
if (lowering.IsSideEffectFree()) {
node = lowering.value();
} else {
DCHECK(!lowering.Changed());
node = ProcessCallArguments(op, args, static_cast<int>(arg_count));
}
environment()->BindAccumulator(node, Environment::kAttachFrameState);
}
...
...
const Operator* JSOperatorBuilder::Call(size_t arity, CallFrequency frequency,
VectorSlotPair const& feedback,
ConvertReceiverMode convert_mode,
SpeculationMode speculation_mode) {
DCHECK_IMPLIES(speculation_mode == SpeculationMode::kAllowSpeculation,
feedback.IsValid());
CallParameters parameters(arity, frequency, feedback, convert_mode,
speculation_mode);
return new (zone()) Operator1<CallParameters>( // --
IrOpcode::kJSCall, Operator::kNoProperties, // opcode
"JSCall", // name
parameters.arity(), 1, 1, 1, 1, 2, // inputs/outputs
parameters); // parameter
}