v8-Torque-builtins

542 阅读8分钟

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的优势:

  1. 类型安全 - Torque提供了静态类型检查,这有助于在编译阶段捕获错误。

  2. 易于维护 - 相比C++,Torque的语法更简洁,使得内置函数的编写和维护更加容易。

  3. 性能优化 - Torque允许编写高效的底层代码,这对于提升V8引擎的性能至关重要。

Torque的编译:

Torque编写的内置函数最终会被转换成C++代码,然后由V8的编译器进一步编译成目标平台的机器码,而不是V8的字节码。这样可以确保V8引擎能够以最高效的方式执行JavaScript代码。

Torque生成的机器码是在V8引擎的编译时完成的,它是引擎构建过程的一部分。这意味着当V8引擎发布时,Torque编写的内置函数已经被编译成了机器码,并且这些代码随着V8引擎的其他部分一起被链接成最终的产品。

Builtins

在v8中,builtins可以看做是在VM运行时可执行的代码块。常见的例子是实现内置对象(RegExp 、Promise)的一些方法。但是内置程序也可以用来提供其他内部功能,如:IC system。

v8内置程序可以使用多种方法来实现:

  1. Platform-dependent assembly language - 特定平台的汇编语言用于编写性能关键的内置函数。这些汇编代码直接操作CPU指令,提供最高的执行速度,但它们的可移植性较差,因为每种CPU架构的汇编语言都不相同。

  2. C++ - C++是实现V8内置程序的传统方式。C++代码相对于汇编语言更容易编写和维护,同时也能提供较高的性能。V8的很多核心功能都是用C++实现的。

  3. JavaScript - 简单易读的代码,速度较慢,JavaScript builtins 已经过时不应该在添加了。

  4. CodeStubAssembler - 提供非常接近汇编语言的高效低级功能,同时保持平台无关性和可读性。

  5. V8 Torque- 是一个特定于 v8的领域特定语言,它被转换为 codestubAssembler。扩展了 CodeStubAssembler 并提供了静态类型以及可读性和表达性语法。已经取代了CodeStubAssembler作为内置方法。

C++的方式与Torque方式的区别:

  1. 编写目的:

    C++ - 在V8中,C++通常用于编写复杂的逻辑或性能敏感的代码,同时也用于实现V8引擎的底层结构和API。

    Torque - Torque专用于生成V8内置程序,它可以自动处理类型检查、内存布局和垃圾回收等细节,简化了内置程序的开发。

  2. 性能考量:

    C++ - C++代码需要手动优化以达到高性能,开发者需要具备深入的性能优化知识。

    Torque - Torque生成的代码自动进行一些优化,减少了手动优化的需要。

  3. 可维护性和安全性:

    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"