总览
1 | v8::internal::GeneratedCode |
Compiler::CompileOptimized(function, ConcurrencyMode::kNotConcurrent)
参数1是要compile的function,参数2是一个标志,应该是和线程相关,表示function不在“正在编译的函数的队列”里。
1 | if (mode == ConcurrencyMode::kConcurrent) { |
在这个函数中进行编译,这个函数首先检查function是否已经被编译过了。
1 | if (function->IsOptimized()) return true; |
然后进行编译优化,如果编译优化成功则以后在js中调用函数都执行编译后的code
1 | if (!GetOptimizedCode(function, mode).ToHandle(&code)) { |
如果失败,则回到解释帧InterpreterEntryTrampoline执行
1 | code = BUILTIN_CODE(isolate, InterpreterEntryTrampoline); |
bool GetOptimizedCodeNow(OptimizedCompilationJob* job, Isolate* isolate)
1 | if (job->PrepareJob(isolate) != CompilationJob::SUCCEEDED || |
CompilationJob::Status OptimizedCompilationJob::PrepareJob
v8里有非常多的status,很有意思。
1 | DEFINE_BOOL(trace_opt, false, "trace lazy optimization") |
这个函数就是调用
1 | return UpdateState(PrepareJobImpl(isolate), State::kReadyToExecute); |
PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl
前面根据一些标志位进行设置,包括下面这些等。
1 | // OptimizedCompilationInfo encapsulates the information needed to compile |
然后开始创建Graph
1 | if (!pipeline_.CreateGraph()) { |
PipelineImpl::CreateGraph()
检查trace标志位并做相应操作,主要是做记录。
1 | if (info()->trace_turbo_json_enabled() || |
通过添加修饰器来记录源码位置。
1 | data->source_positions()->AddDecorator(); |
然后
1 | Run<GraphBuilderPhase>(); |
BytecodeGraphBuilder::CreateGraph()
设置图的基本结构。
{Start}的输出是形参(包括receiver)加上new target, arguments数目,context和closure
1 | int actual_parameter_count = bytecode_array()->parameter_count() + 4; |
NewNode是用于创建新node的帮助函数。
common()返回CommonOperatorBuilder*的common_,差不多是一个op的集合了,然后从中选择Start
1 | CommonOperatorBuilder* common() const { return common_; } |
这个文件还是比较有用的,common-operator.cc,因为NewNode的opcode参数从这里初始化。
回到NewNode看一下
1 | // Factory template for nodes with static input counts. |
最后是执行到了这里,于是我们来分析一下这个函数。
Node::New
1 | Node* Node::New(Zone* zone, NodeId id, const Operator* op, int input_count, |
首先定义了几个局部变量
1 | Node** input_ptr; |
然后判断input_count是否大于kMaxInineCapacity
注意这里的input_count来自这里的nodes_arr.size(),此处对于start的情况,NewNode(common()->Start(actual_parameter_count)));
,可以看出这个结果是0。
这是个比较特殊的情况,后面我们再分析几个node的生成看一下这个逻辑。
1 | Node* NewNode(const Operator* op, Nodes*... nodes) { |
为什么这里有这样的一个比较呢?是因为v8对node的存储决定的
从注释里可以找到
1 |
|
如果是小于kMaxInineCapacity,则可以直接将inputs内联在node中。
这里的计算方法是,首先计算capacity,默认应该是等于input_count,如果有has_extensible_inputs
,则在input_count + 3和kMaxInlineCapacity选取一个最小值。
这个has_extensible_inputs我还不是很懂,后面看看吧
1 | int capacity = input_count; |
然后计算size大小,并为node和它的use/input分配内存。
1 | size_t size = sizeof(Node) + capacity * (sizeof(Node*) + sizeof(Use)); |
顺便说一下,一个Use大小是24字节,一个Node是40字节
计算好size之后进入这个函数,在这生成新的node。
1 | Node::Node(NodeId id, const Operator* op, int inline_count, int inline_capacity) |
最后需要为这个node建立input/use关系,这里的逻辑就是,首先根据当前node的input_count数。
依次设置to为input节点。
1 | for (int current = 0; current < input_count; ++current) { |
然后由于input_ptr指向node的inputs区域,在node的inputs区域记录它的input节点的地址。
1 | input_ptr = node->inputs_.inline_; |
通过这种方式就将节点的input关系建立好了。
然后需要考虑一下use关系,现在我们可以看到use_ptr指向的是当前node的地址。
通过use_ptr和{input_index}来计算出use,然后在use里记录当前{input_index}的值,于是我们可以通过这个值来做简单的算数计算来找到node。
1 | Use* use = use_ptr - 1 - current; |
然后
1 | to->AppendUse(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 | // The abstract execution environment simulates the content of the interpreter |
从上面可知,values()返回一个NodeVector values_。
然后继续看env的初始化
1 | Environment env(this, bytecode_array()->register_count(), |
注意这句话
1 | parameter[0] is the receiver (this), parameters 1..N are the |
首先创建parameter节点,Start作为parameter的input节点。
1 | for (int i = 0; i < parameter_count; i++) { |
然后向values_这个NodeVector的end之前,插入register_count个值为undefined_constant的Node节点。
1 | // Registers |
先从cache中检查是否已经有HeapConstant,如果没有就新建再返回,如果有就直接返回cache里的。
然后再向value_的最后插入一个undefined_constant节点作为Accumulator。
1 | // Accumulator |
然后设置Context
1 | // Context |
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 | BytecodeAnalysis bytecode_analysis(bytecode_array(), local_zone(), |
如果想观察liveness过程,可以启用这个flagDEFINE_BOOL(trace_environment_liveness, false,
"trace liveness of local variable slots")
bytecode_array被设置迭代,然后通过VisitSingleBytecode一个个处理。
1 | for (; !iterator.done(); iterator.Advance()) { |
这个函数前面就是一些获取bytecode并偏移寻找下一个还有一些其他判断,主要的内容其实是这个大的switch case,对不同bytecode进行不同处理。
1 | switch (iterator.current_bytecode()) { |
BYTECODE_LIST在bytecode.h里,太长了就不列了。
VisitSingleBytecode里有很多分支,我捡一些写一下。
1 | [generated bytecode for function: foo] |
- 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
39void 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);
} 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);
}
}
} - VisitLdaNamedProperty
1
2
3
4
5
6
7
8
9const 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 | void BytecodeGraphBuilder::BuildCall(ConvertReceiverMode receiver_mode, |