CodeStubAssembler builtins
This document is intended as an introduction to writing CodeStubAssembler builtins, and is targeted towards V8 developers.
注意: Torque代替CodeStubAssembler作为实现新内置函数的推荐方法. 有关本指南的"扭矩"版本,请参阅" 扭矩内置件".
Builtins
在V8中,内置程序可以看作是VM在运行时可执行的代码块. 一个常见的用例是实现内置对象的功能(例如RegExp或Promise),但是内置对象也可以用于提供其他内部功能(例如,作为IC系统的一部分).
V8的内置函数可以使用多种不同的方法来实现(每种方法都有不同的权衡):
- 平台相关的汇编语言 :可能高效,但是需要手动移植到所有平台,并且难以维护.
- C ++ :风格与运行时功能非常相似,可以访问V8强大的运行时功能,但通常不适合对性能敏感的领域.
- JavaScript :简洁明了的代码,可访问快速的内在函数,但频繁使用慢速运行时调用,会因类型污染而导致无法预测的性能,以及有关(复杂且非显而易见的)JS语义的细微问题.
- CodeStubAssembler :提供高效的底层功能,该功能非常类似于汇编语言,同时保持平台无关性并保持可读性.
剩余的文档主要针对后者,并提供了一个简短的教程,以开发内置于JavaScript的简单CodeStubAssembler(CSA).
CodeStubAssembler
V8的CodeStubAssembler是一个定制的,与平台无关的汇编器,它提供低级原语作为对汇编的精简抽象,但还提供了广泛的高级功能库.
// Low-level:
// Loads the pointer-sized data at addr into value.
Node* addr = /* ... */;
Node* value = Load(MachineType::IntPtr(), addr);
// And high-level:
// Performs the JS operation ToString(object).
// ToString semantics are specified at https://tc39.es/ecma262/#sec-tostring.
Node* object = /* ... */;
Node* string = ToString(context, object);
CSA内建程序通过TurboFan编译管道的一部分运行(包括块调度和寄存器分配,但特别是不通过优化过程),然后发出最终的可执行代码.
Writing a CodeStubAssembler builtin
在本节中,我们将编写一个简单的CSA内置函数,它接受一个参数,并返回它是否代表数字42
. 通过将其安装在Math
对象上(因为可以),可以将内建函数暴露给JS.
此示例说明:
- 创建带有JavaScript链接的内置CSA,可以像JS函数一样调用它.
- 使用CSA实现简单的逻辑:Smi和堆编号处理,条件和对TFS内置函数的调用.
- 使用CSA变量.
- 在
Math
对象上内置的CSA的安装.
如果您想在本地使用,以下代码基于7a8d20a7版本.
Declaring MathIs42
内置BUILTIN_LIST_BASE
在src/builtins/builtins-definitions.h
BUILTIN_LIST_BASE
src/builtins/builtins-definitions.h
BUILTIN_LIST_BASE
src/builtins/builtins-definitions.h
中的BUILTIN_LIST_BASE
宏中声明. 要创建带有JS链接和一个名为X
参数的内置CSA,请执行以下操作:
#define BUILTIN_LIST_BASE(CPP, API, TFJ, TFC, TFS, TFH, ASM, DBG) \
// […snip…]
TFJ(MathIs42, 1, kX) \
// […snip…]
请注意, BUILTIN_LIST_BASE
采用几个表示不同的内置种类的宏(有关更多详细信息,请参见内联文档). CSA内置插件专门分为:
- TFJ :JavaScript链接.
- TFS :存根链接.
- TFC :内置存根链接,需要自定义接口描述符(例如,如果参数未加标签或需要在特定的寄存器中传递).
- TFH :内置的专用存根链接,用于IC处理程序.
Defining MathIs42
内置定义位于src/builtins/builtins-*-gen.cc
文件中,按主题大致组织. 由于我们将编写Math
内置函数,因此将定义放入src/builtins/builtins-math-gen.cc
.
// TF_BUILTIN is a convenience macro that creates a new subclass of the given
// assembler behind the scenes.
TF_BUILTIN(MathIs42, MathBuiltinsAssembler) {
// Load the current function context (an implicit argument for every stub)
// and the X argument. Note that we can refer to parameters by the names
// defined in the builtin declaration.
Node* const context = Parameter(Descriptor::kContext);
Node* const x = Parameter(Descriptor::kX);
// At this point, x can be basically anything - a Smi, a HeapNumber,
// undefined, or any other arbitrary JS object. Let’s call the ToNumber
// builtin to convert x to a number we can use.
// CallBuiltin can be used to conveniently call any CSA builtin.
Node* const number = CallBuiltin(Builtins::kToNumber, context, x);
// Create a CSA variable to store the resulting value. The type of the
// variable is kTagged since we will only be storing tagged pointers in it.
VARIABLE(var_result, MachineRepresentation::kTagged);
// We need to define a couple of labels which will be used as jump targets.
Label if_issmi(this), if_isheapnumber(this), out(this);
// ToNumber always returns a number. We need to distinguish between Smis
// and heap numbers - here, we check whether number is a Smi and conditionally
// jump to the corresponding labels.
Branch(TaggedIsSmi(number), &if_issmi, &if_isheapnumber);
// Binding a label begins generating code for it.
BIND(&if_issmi);
{
// SelectBooleanConstant returns the JS true/false values depending on
// whether the passed condition is true/false. The result is bound to our
// var_result variable, and we then unconditionally jump to the out label.
var_result.Bind(SelectBooleanConstant(SmiEqual(number, SmiConstant(42))));
Goto(&out);
}
BIND(&if_isheapnumber);
{
// ToNumber can only return either a Smi or a heap number. Just to make sure
// we add an assertion here that verifies number is actually a heap number.
CSA_ASSERT(this, IsHeapNumber(number));
// Heap numbers wrap a floating point value. We need to explicitly extract
// this value, perform a floating point comparison, and again bind
// var_result based on the outcome.
Node* const value = LoadHeapNumberValue(number);
Node* const is_42 = Float64Equal(value, Float64Constant(42));
var_result.Bind(SelectBooleanConstant(is_42));
Goto(&out);
}
BIND(&out);
{
Node* const result = var_result.value();
CSA_ASSERT(this, IsBoolean(result));
Return(result);
}
}
Attaching Math.Is42
诸如Math
类的内置对象主要是在src/bootstrapper.cc
设置的(某些设置在.js
文件中进行). 附加新的内置函数很简单:
// Existing code to set up Math, included here for clarity.
Handle<JSObject> math = factory->NewJSObject(cons, TENURED);
JSObject::AddProperty(global, name, math, DONT_ENUM);
// […snip…]
SimpleInstallFunction(math, "is42", Builtins::kMathIs42, 1, true);
现在已经附加了Is42
,可以从JS调用它:
$ out/debug/d8
d8> Math.is42(42);
true
d8> Math.is42('42.0');
true
d8> Math.is42(true);
false
d8> Math.is42({ valueOf: () => 42 });
true
Defining and calling a builtin with stub linkage
也可以使用存根链接(而不是上面在MathIs42
使用的JS链接)创建CSA内置MathIs42
. 这样的内建函数对于将常用的代码提取到可以由多个调用者使用的单独的代码对象中很有用,而该代码仅生成一次. 让我们将用于处理堆编号的代码提取到名为MathIsHeapNumber42
的单独内置MathIsHeapNumber42
,然后从MathIs42
调用.
定义和使用TFS存根很容易. 声明再次放置在src/builtins/builtins-definitions.h
:
#define BUILTIN_LIST_BASE(CPP, API, TFJ, TFC, TFS, TFH, ASM, DBG) \
// […snip…]
TFS(MathIsHeapNumber42, kX) \
TFJ(MathIs42, 1, kX) \
// […snip…]
请注意,当前, BUILTIN_LIST_BASE
中的顺序BUILTIN_LIST_BASE
重要. 由于MathIs42
调用MathIsHeapNumber42
,因此前者需要在后者之后列出(此要求应在某个时候取消).
定义也很简单. 在src/builtins/builtins-math-gen.cc
:
// Defining a TFS builtin works exactly the same way as TFJ builtins.
TF_BUILTIN(MathIsHeapNumber42, MathBuiltinsAssembler) {
Node* const x = Parameter(Descriptor::kX);
CSA_ASSERT(this, IsHeapNumber(x));
Node* const value = LoadHeapNumberValue(x);
Node* const is_42 = Float64Equal(value, Float64Constant(42));
Return(SelectBooleanConstant(is_42));
}
最后,让我们从MathIs42
调用我们的新内置MathIs42
:
TF_BUILTIN(MathIs42, MathBuiltinsAssembler) {
// […snip…]
BIND(&if_isheapnumber);
{
// Instead of handling heap numbers inline, we now call into our new TFS stub.
var_result.Bind(CallBuiltin(Builtins::kMathIsHeapNumber42, context, number));
Goto(&out);
}
// […snip…]
}
您为什么要完全关心TFS内置函数? 为什么不保留代码内联(或提取到帮助方法中以提高可读性)?
一个重要的原因是代码空间:内建函数是在编译时生成的,并包含在V8快照中,因此无条件地在每个创建的隔离中占用(大量)空间. 将大量常用代码提取到TFS内置文件中,可以快速节省10至100 KB的空间.
Testing stub-linkage builtins
即使我们的新内置函数使用非标准(至少是非C ++)的调用约定,也可以为其编写测试用例. 可以将以下代码添加到test/cctest/compiler/test-run-stubs.cc
以测试所有平台上的内置程序:
TEST(MathIsHeapNumber42) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Heap* heap = isolate->heap();
Zone* zone = scope.main_zone();
StubTester tester(isolate, zone, Builtins::kMathIs42);
Handle<Object> result1 = tester.Call(Handle<Smi>(Smi::FromInt(0), isolate));
CHECK(result1->BooleanValue());
}