V8 内置函数(Built-in Functions)完全指南:从 JavaScript 实现到 Runtime 与 CSA 的多种实现路径
2026/9/20 22:51:39 网站建设 项目流程

V8 内置函数(Built-in Functions)完全指南:从 JavaScript 实现到 Runtime 与 CSA 的多种实现路径

【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8

本文基于官方文档 docs/builtin-functions.md 编写,深入剖析 V8 引擎中"内置函数"的多种实现形态:为什么有的内置函数用 JavaScript 写、有的用 C++ 写、有的用汇编写,以及它们之间如何协作。读完本文,你将掌握 V8 内置函数的完整分类体系、%前缀运行时函数的调用机制、--allow-natives-syntax调试标志的用法,以及如何在源码中定位每一种实现的入口点。

一、什么是 V8 的 Built-in Functions

V8 中,JavaScript 标准库中的绝大多数功能(如Array.prototype.mapObject.createString.prototype.replace等)并非由用户代码实现,而是由 V8 引擎在启动时预先准备好的一系列内置函数(built-ins)提供。根据 docs/builtin-functions.md 的定义,这些内置函数在实现方式上存在多种不同风格,具体取决于其功能特性、性能要求,以及部分历史演进因素。

这意味着:同样是内置函数,Array.prototype.pushObject.create的底层实现可能完全不同——一个可能直接编译为机器码,另一个可能走 C++ 运行时调用。理解这些差异,是深入阅读 V8 源码、分析性能瓶颈和调试引擎行为的基础。

从当前仓库的源码结构看,内置函数的实现主要集中在以下两个目录:

  • src/runtime/:存放运行时函数(Runtime Functions)的声明与实现,核心入口是 src/runtime/runtime.h;
  • src/builtins/:存放 built-ins 本身的实现,包括 C++ 实现、CodeStubAssembler(CSA)实现和架构相关汇编实现,核心入口是 src/builtins/builtins.h。

二、内置函数的第一种形态:直接用 JavaScript 实现

部分内置函数直接用 JavaScript 编写,并在运行时像普通用户 JavaScript 一样被解析、编译为可执行代码。这类实现方式通常用于那些逻辑相对复杂、但对极致性能要求不那么苛刻的功能。

这种做法的好处是:

  • 开发效率高,语义直观,便于维护;
  • 可以利用 V8 的优化编译器(如 Maglev、TurboFan)对其进行自动优化;
  • 与用户代码共享同一套优化管线,无需维护独立的底层实现。

在仓库中,这类"用 JavaScript 编写的内置函数"集中体现在 src/builtins/ 目录下的大量.tq(Torque)文件以及部分以 JavaScript 语义定义的 built-in 中。Torque 是 V8 的领域特定语言,其输出会被编译为 CSA 代码或 Turboshaft reducer,最终由 TurboFan 生成机器码——这既保留了"像写 JS 一样写内置函数"的开发体验,又获得了接近手写汇编的性能。关于 Torque 的详细架构可参考 docs/builtins/architecture.md 与 docs/torque/architecture.md。

三、第二种形态:Runtime Functions(运行时函数)

3.1 什么是 Runtime Functions

部分内置函数的功能部分依赖于所谓的运行时函数(runtime functions)。运行时函数是用 C++ 编写的,JavaScript 通过%前缀来调用它们。

例如,在 V8 内部代码中可以看到类似%ArraySpeciesConstructor(...)%ThrowTypeError(...)的调用——这些都是运行时函数。它们的特点是:

  • 拥有完整的 C++ 实现,可以直接访问 V8 引擎的底层内部结构;
  • 通过%前缀从 JavaScript 侧调用;
  • 通常只限于 V8 内部 JavaScript 代码使用。

3.2 完整的运行时函数清单

所有运行时函数的声明集中维护在 src/runtime/runtime.h 中。该文件通过一组FOR_EACH_INTRINSIC_*宏按功能领域分类列出所有 intrinsic(内置调用原语),例如:

  • FOR_EACH_INTRINSIC_ARRAY:数组相关,如ArrayIndexOfArrayIsArrayGrowArrayElements等;
  • FOR_EACH_INTRINSIC_ATOMICS:Atomics 相关,如AtomicsLoad64AtomicsStore64AtomicsCompareExchange等;
  • FOR_EACH_INTRINSIC_BIGINT:BigInt 相关,如BigIntCompareToNumberBigIntExponentiate等;
  • FOR_EACH_INTRINSIC_CLASSES:class 语义相关,如DefineClassLoadFromSuperStoreToSuper等;
  • FOR_EACH_THROWING_INTRINSIC_CLASSES:抛错类,如ThrowConstructorNonCallableErrorThrowStaticPrototypeError等;
  • FOR_EACH_INTRINSIC_COMPILER:编译器相关,如CompileLazyCompileBaselineNotifyDeoptimizedResolvePossiblyDirectEval等;
  • FOR_EACH_INTRINSIC_TIERING:分层编译(tiering)相关,如OptimizeMaglevEagerOptimizeTurbofanEagerStartMaglevOptimizeJob等;
  • FOR_EACH_INTRINSIC_DEBUG:调试相关,如CollectGarbageDebugOnFunctionCallHandleDebuggerStatement等;
  • FOR_EACH_INTRINSIC_FORINfor-in相关,如ForInEnumerateForInHasProperty
  • FOR_EACH_INTRINSIC_FUNCTION:函数相关,如CallFunctionGetSourceCodeFunctionGetScriptId等;
  • FOR_EACH_INTRINSIC_GENERATOR:生成器/异步函数相关,如AsyncFunctionAwaitAsyncFunctionEnterCreateJSGeneratorObject等;
  • FOR_EACH_INTRINSIC_INTL:国际化相关(在V8_INTL_SUPPORT开启时生效),如FormatListStringToLowerCaseIntl等;
  • FOR_EACH_INTRINSIC_INTERNAL:内部通用功能,如AllocateInYoungGenerationPerformMicrotaskCheckpointStackGuardTerminateExecutionTypeof等。

每个条目通过F(name, nargs, ressize)宏定义,其中:

  • name:intrinsic 的名称;
  • nargs:参数个数,-1表示可变参数(源码注释中会用/* >= 3 */这类形式标注下限);
  • ressize:返回值个数(绝大多数为 1,返回一个 tagged pointer)。
// src/runtime/runtime.h 中的实际条目示例 F(ArrayIndexOf, 3, 1) // 3 个参数,返回 1 个值 F(NewArray, -1 /* >= 3 */, 1) // 可变参数,至少 3 个 F(ThrowTypeError, -1 /* >= 1 */, 1) // 可变参数,至少 1 个 F(BigIntEqualToBigInt, 2, 1, RuntimeCallProperty::kCannotTriggerGC) // 额外声明:不会触发 GC

此外,个别条目会附加RuntimeCallProperty::kCannotTriggerGC属性,声明该调用不会触发垃圾回收,编译器可据此做更激进的优化。

3.3 Intrinsic 的两种暴露形式:%%_

根据 src/runtime/runtime.h 头部注释,每个 intrinsic 在 JavaScript 中有两种暴露形式:

  • %name总是一次运行时调用(对应IntrinsicType::RUNTIME,函数 ID 为Runtime::k##name);
  • %_name(可选):可以被编译器内联,也可以退化为运行时调用,由具体编译器决定(对应IntrinsicType::INLINE,函数 ID 为Runtime::kInline##name)。

无论哪种形式,所有 intrinsic 都拥有一个 C++ 实现,命名统一为Runtime_##name

#define F(name, nargs, ressize, ...) \ Address Runtime_##name(int args_length, Address* args_object, \ Isolate* isolate); FOR_EACH_INTRINSIC_RETURN_OBJECT(F)

而每个编译器(Ignition、Sparkplug、Maglev、TurboFan)都维护一份自己支持的 intrinsic 列表,对于不支持内联的 intrinsic 会回退为普通运行时调用。同时,Runtime::Function结构体(src/runtime/runtime.h)记录了每个 intrinsic 的function_id、类型、JS 名称、C++ 入口地址、参数个数与返回值个数,整个运行时函数表可通过Runtime::RuntimeFunctionTable(isolate)获取。

3.4%前缀的限制与--allow-natives-syntax标志

这些运行时函数通常只限于 V8 内部 JavaScript 代码使用。出于调试目的,如果 V8 以--allow-natives-syntax标志运行,普通 JavaScript 代码中也可以调用它们。

在 src/flags/flag-definitions.h 中可以找到该标志的定义:

DEFINE_BOOL(allow_natives_syntax, false, "allow natives syntax") DEFINE_BOOL(allow_natives_for_differential_fuzzing, false, "allow only natives explicitly allowlisted for differential " "fuzzing") DEFINE_IMPLICATION(allow_natives_for_differential_fuzzing, allow_natives_syntax) DEFINE_IMPLICATION(allow_natives_for_differential_fuzzing, fuzzing)

要点:

  • 默认值为false,即普通构建下%语法不可用;
  • allow_natives_for_differential_fuzzing是更受限的变体,只放行明确列入差分模糊测试白名单的 natives,且开启它隐式开启allow_natives_syntaxfuzzing
  • 该标志主要用于 V8 自身的测试(尤其是test/mjsunit/下的测试)与差分模糊测试(differential fuzzing)。

典型用法(以 d8 运行):

# 允许在 JS 代码中使用 % 前缀调用运行时函数 d8 --allow-natives-syntax test.js # 调试标志与常用调试手段组合使用 d8 --allow-natives-syntax --trace-opt test.js

在测试代码中,常见做法是利用%OptimizeFunctionOnNextCall%GetOptimizationStatus等 intrinsic 来手动触发并检查优化状态——Runtime::OptimizationStatus枚举(src/runtime/runtime.h)正是为这类检查设计的,其中的位定义需要与test/mjsunit/mjsunit.js保持同步(源码注释明确说明了这一点)。这就是为什么 mjsunit 测试在头部通常会带上// Flags: --allow-natives-syntax这样的注释。

注意:Runtime::IsEnabledForFuzzing()(src/runtime/runtime.h)表明并非所有 intrinsic 都向 fuzzer 开放,只有明确放行的子集才可用于差分模糊测试,这与上面的allow_natives_for_differential_fuzzing标志的设计互相印证。

3.5 运行时函数的入口签名

所有运行时函数拥有统一的 C++ 入口签名,且通过RUNTIME_FUNCTION宏展开(定义于 src/runtime/runtime.cc 附近):

Address Runtime_##name(int args_length, Address* args_object, Isolate* isolate);

其中args_length为参数数量,args_object为参数数组,isolate为当前隔离区。runtime.h中还提供了一系列查询接口,例如Runtime::NeedsExactContext(id)判断是否依赖"当前上下文"、Runtime::IsNonReturning(id)判断是否必定抛异常(永不正常返回)、Runtime::MayAllocate(id)判断是否可能触发堆分配,这些元信息被编译器和 GC 相关逻辑广泛使用。

四、第三种形态:Built-ins 的多种实现方式

除 JavaScript 实现和 runtime functions 之外,另一些函数被实现为built-ins,而 built-ins 本身又有数种不同的实现手段。根据 docs/builtin-functions.md 与 docs/builtins/architecture.md 的归纳,主要有以下几种。

4.1 平台相关汇编实现(Architecture-Specific Assembler)

部分 built-ins直接用平台相关的汇编(assembly)实现。这类实现位于 src/builtins/ 下的架构子目录中,例如x64arm64ia32riscvmips64loong64ppcs390等(对应src/builtins/[arch]/builtins-[arch].cc)。

它们通过 V8 的MacroAssembler直接发射原始机器指令,用于:

  • 底层入口点(entry points)与跳板(trampolines);
  • 需要操纵调用栈的操作(压栈参数、建立解释器栈帧等)——因为 TurboFan 生成的代码假定固定的栈布局,这类操作只能用架构相关 built-in 完成;
  • 对每条指令都极为敏感的热点路径。

代价是:必须为每个支持的架构分别编写。在 src/builtins/builtins-definitions.h 中,这类 built-in 用ASM(name, interface_descriptor)宏登记,例如反优化入口ASM(DeoptimizationEntry_Eager, DeoptimizationEntry)、写屏障TFC(RecordWriteSaveFP, WriteBarrier)等。

4.2 CodeStubAssembler(CSA)实现

还有一部分 built-ins 使用CodeStubAssembler(CSA)实现——一个平台无关的抽象层。CSA 提供一套 C++ API,用编程方式直接发射 TurboFan 图节点(如ParameterLoadBranchCallRuntime),绕过了 JavaScript 的解析器和 AST,最终由 TurboFan 后端生成机器码。

CSA 的实现文件命名规律为builtins-*-gen.cc/builtins-*.h(位于 src/builtins/)。在 src/builtins/builtins-definitions.h 中,CSA/TurboFan built-ins 按链接约定分为:

含义参数说明
CPP纯 C++ built-in,通过BUILTIN_EXITframe 进入name, formal parameter count
TFJTurboFan 实现,JS 链接(可作为 JavaScript 函数调用)name, formal parameter count, explicit argument names...
TFSTurboFan 实现,CodeStub 链接name, needs context, explicit argument names...
TFCTurboFan 实现,CodeStub 链接且带自定义描述符name, interface descriptor
TFHTurboFan 实现的处理器(handler),CodeStub 链接name, interface descriptor
BCH字节码处理器(bytecode handler),带字节码分发链接name, OperandScale, Bytecode
ASM平台相关汇编 built-inname, interface descriptor

同类的TFJ_TSATFC_TSABCH_TSA则是使用Turboshaft Assembler定义的版本(V8 正逐步从 CSA 迁移到 Turboshaft,参见 docs/builtins/architecture.md)。

built-ins 有时也被用来实现胶水代码(glue code)片段,而不一定是完整函数。例如 src/builtins/builtins-definitions.h 中的LOAD_IC_HANDLER_LISTLOAD_IC_IN_OBJECT_FIELD_WITH_INDEX_HANDLER_LIST等宏,生成的是大量内联缓存(IC)处理器——它们不是"标准库函数",而是执行属性加载/存储的微型代码片段,每个对应一种字段布局(如 in-object 非 double 字段、out-of-object 字段、来自原型的常量等)。这正是官方文档所说"glue code,不一定是完整函数"的典型例证。

4.3 纯 C++ 实现

还有一部分 built-ins直接用 C++ 实现。这类函数以BUILTIN(Name)宏定义,例如 src/builtins/builtins-array.cc 中的:

BUILTIN(ArrayPrototypeFill) { ... } // src/builtins/builtins-array.cc BUILTIN(ArrayPush) { ... } BUILTIN(ArrayPop) { ... } BUILTIN(ArrayShift) { ... } BUILTIN(ArrayUnshift) { ... } BUILTIN(ArrayConcat) { ... }

纯 C++ built-in 的优缺点:

  • 优点:最容易编写,拥有完整 C++ 标准库访问权限,可以触及 V8 复杂的内部结构(堆、句柄、对象布局等);
  • 缺点:由于需要在 JS 与 C++ 之间跨边界(boundary crossing),性能通常低于 Torque/CSA 实现

4.4 Builtins 的完整清单与枚举

所有 built-ins 的完整清单由 src/builtins/builtins-definitions.h 中的BUILTIN_LIST(...)宏家族汇总(它同时引入bytecodes-builtins-list.htorque-generated/builtin-definitions.h),而 src/builtins/builtins.h 则通过BUILTIN_LIST展开出enum class Builtin枚举,并为每个枚举值提供k##Name形式:

enum class Builtin : int32_t { kNoBuiltinId = -1, #define DEF_ENUM(Name, ...) k##Name, BUILTIN_LIST(DEF_ENUM, ...) #undef DEF_ENUM kFirstBytecodeHandler = ..., kFirstLoadICHandler = ..., kLastLoadICHandler = kFirstLoadICHandler + kBuiltinLoadICHandlerCount - 1 };

同时,src/builtins/builtins.h 提供便捷宏BUILTIN_CODE(isolate, name),用于在 C++ 侧获取指定 built-in 的 code handle:

#define BUILTIN_CODE(isolate, name) \ (isolate)->builtins()->code_handle(i::Builtin::k##name)

Builtins类(src/builtins/builtins.h)负责管理这些 built-in 的加载、反汇编支持(Lookup(Address pc))以及 tier0/tier1 分层(kBuiltinCountkBuiltinTier0Count等常量)。tier0 built-in保证靠近 root register,可以被高效地以 root-relative 方式调用(src/builtins/builtins-definitions.h 明确指出 tier0 集合必须尽可能小,且不应随意新增)。

五、Built-ins 的加载与嵌入机制

built-ins 在V8 构建过程中(或不使用快照时的启动过程中)被编译,并存储于**快照(Snapshot)**中,从而保证 V8 启动时所有标准库函数已就绪,无需在运行时解析和编译它们。同时,同一进程内的所有 Isolate 与 context共享这些 built-ins(参见 docs/builtins/architecture.md)。

为了降低启动时间并支持多 Isolate 间的内存共享,V8 将 built-ins嵌入二进制文件:

  • Embedded Blob:built-ins 被编译并写入二进制的一个专用段(embedded blob);
  • 共享:同一进程的多个 Isolate 共享同一份内置可执行代码,节省内存。

在某些条件下,V8 会把嵌入的 built-ins复制或重映射(remap)到某个 Isolate 的CodeRange中:

  • 在 x64、arm64 等架构上,函数调用只有有限的相对寻址范围。如果CodeRange中生成的代码需要调用 built-in,而 embedded blob 在内存中距离太远,就无法使用高效的 PC 相对调用;
  • v8_flags.short_builtin_calls开启(通常取决于可用物理内存)时,V8 会把这些 built-ins 复制/重映射到CodeRange中,使其靠近生成的代码;
  • 若操作系统支持页重映射,则采用remap(保持共享,避免制造私有脏页);否则退化为memcpy 复制(会增加私有内存占用)。

六、总结:如何选择实现方式

综合 docs/builtin-functions.md 与 docs/builtins/architecture.md 的说明,可以总结出 V8 内置函数实现方式的选择逻辑:

实现方式代表位置优点缺点典型用途
JavaScript(含 Torque)src/builtins/*.tq开发效率高、语义直观、可被优化管线优化需要经过解析/编译标准库方法的主体逻辑
Runtime Functions(C++)src/runtime/runtime.ccruntime.h声明完整访问引擎内部、实现复杂语义JS/C++ 边界开销抛错、GC 触发、编译器辅助等
CodeStubAssembler / Turboshaftsrc/builtins/builtins-*-gen.cc平台无关、高性能、贴近底层编写门槛高热点内建方法、IC 处理器、胶水代码
平台相关汇编src/builtins/[arch]/builtins-[arch].cc极致性能、可精确操控栈每个架构都要写一份入口点、跳板、栈帧操作
纯 C++(BUILTIN 宏)src/builtins/builtins-*.cc最容易写、功能完整跨边界性能较差复杂但不极热的标准库函数

因此,"built-ins 以多种方式实现"并非历史包袱,而是 V8 在开发效率、平台独立性、性能与底层控制力之间做出的工程权衡。理解这条主线之后,再去看 src/builtins/ 和 src/runtime/ 目录下的源码,就能快速判断每个函数背后的实现路径与性能特征。

延伸阅读

  • docs/builtins/architecture.md:built-ins 架构的深入说明(四种 flavor、加载与嵌入机制);
  • src/builtins/builtins.h:Builtin枚举与Builtins类;
  • src/builtins/builtins-definitions.h:全部 built-in 的宏清单(CPP/TFJ/TFS/TFC/TFH/BCH/ASM 分类);
  • src/runtime/runtime.h:全部运行时函数(intrinsic)的宏清单与Runtime::Function结构;
  • docs/torque/architecture.md:Torque 语言架构;
  • docs/codegen/code-stub-assembler.md:CodeStubAssembler 详解;
  • src/flags/flag-definitions.h:--allow-natives-syntax等调试标志定义。

【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询