Hello World
如何运行源码例程
构建、运行源码目录下的 samples/hello-world.cc
gn gen out/x64_hello_world.debug --args='v8_monolithic=true v8_use_external_startup_data=false is_component_build=false is_debug=false target_cpu="x64" use_goma=false goma_dir="None" v8_enable_backtrace=true v8_enable_disassembler=true v8_enable_object_print=true v8_enable_verify_heap=true'
ninja -C out/x64_hello_world.debug
介绍 V8 Torque builtins
Torque是V8引擎的一部分,它是一种用于编写V8内置函数的领域特定语言(DSL)。Torque语言被设计用来替代之前使用C++编写的内置函数。它提供了一种更简单、更安全的方式来实现V8引擎中的内置功能,同时也使得这些功能的维护和优化更加高效。
Torque的优势:
-
类型安全 - Torque提供了静态类型检查,这有助于在编译阶段捕获错误。
-
易于维护 - 相比C++,Torque的语法更简洁,使得内置函数的编写和维护更加容易。
-
性能优化 - Torque允许编写高效的底层代码,这对于提升V8引擎的性能至关重要。
Torque的编译:
Torque编写的内置函数最终会被转换成C++代码,然后由V8的编译器进一步编译成目标平台的机器码,而不是V8的字节码。这样可以确保V8引擎能够以最高效的方式执行JavaScript代码。
Torque生成的机器码是在V8引擎的编译时完成的,它是引擎构建过程的一部分。这意味着当V8引擎发布时,Torque编写的内置函数已经被编译成了机器码,并且这些代码随着V8引擎的其他部分一起被链接成最终的产品。
Builtins
在v8中,builtins可以看做是在VM运行时可执行的代码块。常见的例子是实现内置对象(RegExp 、Promise)的一些方法。但是内置程序也可以用来提供其他内部功能,如:IC system。
v8内置程序可以使用多种方法来实现:
-
Platform-dependent assembly language - 特定平台的汇编语言用于编写性能关键的内置函数。这些汇编代码直接操作CPU指令,提供最高的执行速度,但它们的可移植性较差,因为每种CPU架构的汇编语言都不相同。
-
C++ - C++是实现V8内置程序的传统方式。C++代码相对于汇编语言更容易编写和维护,同时也能提供较高的性能。V8的很多核心功能都是用C++实现的。
-
JavaScript - 简单易读的代码,速度较慢,JavaScript builtins 已经过时不应该在添加了。
-
CodeStubAssembler - 提供非常接近汇编语言的高效低级功能,同时保持平台无关性和可读性。
-
V8 Torque- 是一个特定于 v8的领域特定语言,它被转换为 codestubAssembler。扩展了 CodeStubAssembler 并提供了静态类型以及可读性和表达性语法。已经取代了CodeStubAssembler作为内置方法。
C++的方式与Torque方式的区别:
-
编写目的:
C++ - 在V8中,C++通常用于编写复杂的逻辑或性能敏感的代码,同时也用于实现V8引擎的底层结构和API。
Torque - Torque专用于生成V8内置程序,它可以自动处理类型检查、内存布局和垃圾回收等细节,简化了内置程序的开发。
-
性能考量:
C++ - C++代码需要手动优化以达到高性能,开发者需要具备深入的性能优化知识。
Torque - Torque生成的代码自动进行一些优化,减少了手动优化的需要。
-
可维护性和安全性:
C++ - C++代码可能更难维护,尤其是在涉及底层操作时,也容易出现安全问题如内存泄漏、越界访问等。
Torque - Torque的设计旨在提高代码的可维护性和安全性,通过其DSL特性减少了出错的可能性。
在v8的源码中,builtins函数被放在 src/builtins/* 目录下,可以看到文件里存在内联汇编方法,.tq文件和.cc文件。大部分的内置方法都是.tq实现。
Torque与C++实现的方法可以互相调用。在V8引擎中,Torque和C++都是用来实现内置函数和操作的工具,它们之间设计有接口以便相互操作。
编写一个Torque builtin
编写一个简单的内置 CSA,它接受一个参数,并返回它是否表示数字42。通过将内置程序安装到 Math 对象上。
-
创建一个带有 JavaScript 链接的 Torque 内置函数,可以像 JS 函数那样调用它。
-
使用Torque实现一个简单的逻辑、类型区分、Smi和heap-number处理、条件判断。
-
在 Math 对象上安装 CSA 内置程序。
定义 MathIs42
在 src/builtins/math.tq 中定义 MathIs42 方法。
// ES6 #sec-math.is42
builtin HeapNumberIs42(implicit context: Context)(heapNumber: HeapNumber): Boolean {
return Convert<float64>(heapNumber) == 42 ? True : False;
}
transitioning javascript builtin
MathIs42(js-implicit context: NativeContext)(x: JSAny): Boolean {
const number: Number = ToNumber_Inline(x);
typeswitch (number) {
case (smi: Smi): {
return smi == 42 ? True : False;
}
case (heapNumber: HeapNumber): {
// Instead of handling heap numbers inline, we now call our new builtin.
return HeapNumberIs42(heapNumber);
}
}
}
注册 MathIs42
在 src/init/bootstrapper.cc 注册 MathIs42 方法。
其中 Builtins::kMathIs42 由构件时生成 out/x64.debug/gen/torque-generated/builtin-definitions-tq.h
void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Handle<JSFunction> empty_function) {
SimpleInstallFunction(isolate_, math, "is42", Builtins::kMathIs42, 1, true);
}
测试 MathIs42
D8 Console 过程
创建D8Console实例
src/d8/d8.cc 文件的 Shell::Main 方法为入口方法。创建v8实例对象、 创建D8Console实例。
int Shell::Main(int argc, char* argv[]) {
// ...
v8::V8::InitializePlatform(g_platform.get());
v8::V8::Initialize();
// ...
Isolate* isolate = Isolate::New(create_params); // 创建v8实例
{
D8Console console(isolate); // 创建console对象
Isolate::Scope scope(isolate);
Initialize(isolate, &console); // 执行初始化
PerIsolateData data(isolate);
}
// ...
return result;
}
void Shell::Initialize(Isolate* isolate, D8Console* console,
bool isOnMainThread) {
if (isOnMainThread) {
// Set up counters
if (i::FLAG_map_counters[0] != '\0') {
MapCounters(isolate, i::FLAG_map_counters);
}
// Disable default message reporting.
isolate->AddMessageListenerWithErrorLevel(
PrintMessageCallback,
v8::Isolate::kMessageError | v8::Isolate::kMessageWarning |
v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug |
v8::Isolate::kMessageLog);
}
debug::SetConsoleDelegate(isolate, console); // 给v8实例设置console的代理对象
}
void set_console_delegate(debug::ConsoleDelegate* delegate) {
console_delegate_ = delegate;
}
debug::ConsoleDelegate* console_delegate() { return console_delegate_; }
注册Builtins
注册console相关方法
{ // -- C o n s o l e
Handle<String> name = factory->InternalizeUtf8String("console");
NewFunctionArgs args = NewFunctionArgs::ForFunctionWithoutCode(
name, isolate_->strict_function_map(), LanguageMode::kStrict);
Handle<JSFunction> cons = factory->NewFunction(args);
Handle<JSObject> empty = factory->NewJSObject(isolate_->object_function());
JSFunction::SetPrototype(cons, empty);
Handle<JSObject> console = factory->NewJSObject(cons, AllocationType::kOld);
DCHECK(console->IsJSObject());
JSObject::AddProperty(isolate_, global, name, console, DONT_ENUM);
SimpleInstallFunction(isolate_, console, "debug", Builtins::kConsoleDebug,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "error", Builtins::kConsoleError,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "info", Builtins::kConsoleInfo, 0,
false, NONE);
SimpleInstallFunction(isolate_, console, "log", Builtins::kConsoleLog, 0,
false, NONE);
SimpleInstallFunction(isolate_, console, "warn", Builtins::kConsoleWarn, 0,
false, NONE);
SimpleInstallFunction(isolate_, console, "dir", Builtins::kConsoleDir, 0,
false, NONE);
SimpleInstallFunction(isolate_, console, "dirxml", Builtins::kConsoleDirXml,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "table", Builtins::kConsoleTable,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "trace", Builtins::kConsoleTrace,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "group", Builtins::kConsoleGroup,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "groupCollapsed",
Builtins::kConsoleGroupCollapsed, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "groupEnd",
Builtins::kConsoleGroupEnd, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "clear", Builtins::kConsoleClear,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "count", Builtins::kConsoleCount,
0, false, NONE);
SimpleInstallFunction(isolate_, console, "countReset",
Builtins::kConsoleCountReset, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "assert",
Builtins::kFastConsoleAssert, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "profile",
Builtins::kConsoleProfile, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "profileEnd",
Builtins::kConsoleProfileEnd, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "time", Builtins::kConsoleTime, 0,
false, NONE);
SimpleInstallFunction(isolate_, console, "timeLog",
Builtins::kConsoleTimeLog, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "timeEnd",
Builtins::kConsoleTimeEnd, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "timeStamp",
Builtins::kConsoleTimeStamp, 0, false, NONE);
SimpleInstallFunction(isolate_, console, "context",
Builtins::kConsoleContext, 1, true, NONE);
InstallToStringTag(isolate_, console, "Object");
以log方法举例,Builtins::kConsoleLog 方法在 src/builtins/builtins-console.cc 中被定义。并用宏方法注册到BUILTIN
#define CONSOLE_BUILTIN_IMPLEMENTATION(call, name) \
BUILTIN(Console##call) { \
ConsoleCall(isolate, args, &debug::ConsoleDelegate::call); \
RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate); \
return ReadOnlyRoots(isolate).undefined_value(); \
}
CONSOLE_METHOD_LIST(CONSOLE_BUILTIN_IMPLEMENTATION)
#undef CONSOLE_BUILTIN_IMPLEMENTATION
void ConsoleCall(
Isolate* isolate, const internal::BuiltinArguments& args,
void (debug::ConsoleDelegate::*func)(const v8::debug::ConsoleCallArguments&,
const v8::debug::ConsoleContext&)) {
// some code
(isolate->console_delegate()->*func)(
wrapper,
v8::debug::ConsoleContext(context_id, Utils::ToLocal(context_name)));
}
// 其中宏方法类似以下写法:
#define CONSOLE_BUILTIN_IMPLEMENTATION(isolate, call) \
void (debug::Console::*func)(std::string) = &debug::Console::call; \
ConsoleCall(isolate, func); \
debug::Console* d8_console = new debug::Console();
Isolate* isolate = new Isolate(d8_console);
CONSOLE_BUILTIN_IMPLEMENTATION(isolate, log)
方法调用
日志输出会经过ConsoleCall方法,会调用isolate实例的console代理对象,日志输出实现在 src/d8/d8-console.cc 中:
namespace {
void WriteToFile(const char* prefix, FILE* file, Isolate* isolate,
const debug::ConsoleCallArguments& args) {
if (prefix) fprintf(file, "%s: ", prefix);
for (int i = 0; i < args.Length(); i++) {
HandleScope handle_scope(isolate);
if (i > 0) fprintf(file, " ");
Local<Value> arg = args[i];
Local<String> str_obj;
if (arg->IsSymbol()) arg = Local<Symbol>::Cast(arg)->Description();
if (!arg->ToString(isolate->GetCurrentContext()).ToLocal(&str_obj)) return;
v8::String::Utf8Value str(isolate, str_obj);
int n = static_cast<int>(fwrite(*str, sizeof(**str), str.length(), file));
if (n != str.length()) {
printf("Error in fwrite\n");
base::OS::ExitProcess(1);
}
}
fprintf(file, "\n");
}
} // anonymous namespace
void D8Console::Log(const debug::ConsoleCallArguments& args,
const v8::debug::ConsoleContext&) {
WriteToFile(nullptr, stdout, isolate_, args);
}
编写自定义对象
做个小测试,参考Console的实现过程,设法在JavaScript暴漏一个对象并且实现 getVersion 方法用于打印浏览器版本。
定义实现类
在 src/builtins/ 路径新增实现类 src/builtins/builtins-haha.cc文件**。**
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/api/api-inl.h"
#include "src/builtins/builtins-utils-inl.h"
#include "src/builtins/builtins.h"
#include "src/objects/objects-inl.h"
#include "src/utils/version.h" // 增加获取版本方法
namespace v8 {
namespace internal {
BUILTIN(HahaPrototypeToVersion) { // 定义一个方法HahaPrototypeToVersion
HandleScope scope(isolate);
Factory* const factory = isolate->factory();
Handle<String> version = factory->InternalizeUtf8String(v8::internal::Version::GetVersion());
return *version;
}
} // namespace internal
} // namespace v8
生成枚举
在 src/builtins/builtins-definitions.h 文件下增加如下配置,builtins宏会帮助生成一个 Builtins::kHahaPrototypeToVersion 的枚举值:
/* Haha */ \
CPP(HahaPrototypeToVersion) \
添加到编译文件
在 BUILD.gn 文件下增加一行编译依赖:
"src/builtins/builtins-haha.cc",
注册JavaScript
在 src/init/bootstrapper.cc 文件注册对应的方法,暴露到JavaScript。
SimpleInstallFunction 方法是v8引擎中用于简化安装新函数到JavaScript对象的一个辅助函数。
方法签名解释:
-
V8_NOINLINE 是一个编译器指令,用于告诉编译器不要内联这个函数。内联通常用于小的、频繁调用的函数以减少调用开销。在这里,V8_NOINLINE可能是为了避免增加代码大小或因为函数体较大或不频繁调用。
-
Handle 是函数的返回类型,表示它返回一个指向 JavaScript 函数对象的句柄。
-
SimpleInstallFunction 是函数的名称。
-
Isolate* isolate 是指向当前 V8 实例的指针,用于访问 V8 的各种服务和状态。
-
Handle base 是要在其上安装函数的 JavaScript 对象。
-
const char* name 是要安装的函数的名称。
-
Builtins::Name call 是内建函数的枚举名称,这是要安装的函数的实际 C++ 实现。
-
int len 是函数的长度属性,通常表示函数期望的参数个数。
-
bool adapt 表示是否需要适配器,用于调整函数参数。
-
PropertyAttributes attrs = DONT_ENUM 是函数属性,默认为 DONT_ENUM,表示该属性不应出现在对象的枚举属性中。
{ Handle haha = factory->NewJSObject(isolate_->object_function(), AllocationType::kOld); JSObject::AddProperty(isolate_, global, "haha", haha, DONT_ENUM); SimpleInstallFunction(isolate_, haha, "getVersion", Builtins::kHahaPrototypeToVersion, 0, false, NONE); InstallToStringTag(isolate_, haha, "haha"); }
验证
编译运行d8。
./d8
d8> haha.getVersion()
"8.3.110.9"