v8初始化流程

测试代码

// 触发 Smi
const smiVal = 42;
%DebugPrint(smiVal); // 使用 V8 内部函数打印对象信息
// 触发 HeapNumber
const heapNumberVal = 3.14;
const s = "V8"; // 预分配为 String 对象
const obj = { x: 1 }; // 预分配隐藏类(Hidden Class)
%DebugPrint(heapNumberVal);
// 强制转换为 HeapNumber
const converted = Number(smiVal); // 调用 Number 构造函数
%DebugPrint(converted);
const sum = smiVal + heapNumberVal;

执行结果

执行结果
从执行结构来看,smiVal和converted属于smi对象,heapNumberVal属于heapNumber对象。

对比smi和heap object

在 V8 引擎中,Smi(Small Integer)堆对象(Heap Object) 是两种不同的数据存储机制,它们在内存管理、性能优化和使用场景上有显著区别。以下是它们的核心差异:


1. 存储方式

特性Smi堆对象(如 HeapNumber、Object)
存储位置直接存储在 指针本身(无需堆内存分配)存储在 堆内存 中(需显式分配)
编码规则小整数左移一位,最低位(LSB)标记为 0堆地址右移一位,最低位标记为 1
内存占用无额外内存占用(仅指针空间)需要堆内存(通常 8-16 字节,取决于类型)
示例

Smi42 编码为 0x54(二进制 01010100,LSB=0)。 这里的指针指的是广义的指针,表示栈变量、寄存器、对象属性等
堆对象:地址 0x1000 编码为 0x801(二进制 100000000001,LSB=1)。v8中地址是4/8字节对齐的(即地址是4/8的倍数,和机器字长有关),所以最低2/3位一定是0,右移一位不会导致1丢失


2. 数值范围

系统位数Smi 范围堆对象数值范围
32 位系统-2^302^30-1(约 ±10 亿)所有无法用 Smi 表示的数(如浮点数、大整数)
64 位系统-2^312^31-1(约 ±20 亿)同上
示例

Smi2147483647(32 位 Smi 上限)。
堆对象3.14(浮点数)、2147483648(超出 Smi 范围的整数)。


3. 性能差异

操作Smi堆对象
堆内存分配无需分配(直接编码在指针中)需要堆分配(触发 GC 压力)
数值运算直接操作指针值(极快)需访问堆内存(较慢)
类型检查一次位操作(检查 LSB 是否为 0需访问对象的隐藏类(Hidden Class)

4. 使用场景

场景Smi堆对象
高频小整数循环计数器、临时计算结果、数组索引不适用
大整数/浮点数不适用科学计算、大数运算、对象属性
动态类型转换超出范围时自动转为 HeapNumber直接存储
隐式转换示例
let a = 42;      // Smi
a = 3.14;        // 自动转为 HeapNumber
a = 2147483648;  // 超出 Smi 范围,转为 HeapNumber

5. 垃圾回收(GC)影响

特性Smi堆对象
GC 扫描成本无(不占用堆内存)需要跟踪和回收(增加 GC 压力)
内存碎片可能产生碎片(频繁分配/释放时)

通过区分 Smi 和堆对象,V8 在保持 JavaScript 动态类型灵活性的同时,优化了高频操作的性能,减少了内存开销。
程序的整体执行流程如下
v8执行流程


入口函数

主函数int main调用
v8::Shell::Main

int main(int argc, char* argv[]) { return v8::Shell::Main(argc, argv); }

Shell::Main调用Shell::RunMain

int Shell::Main(int argc, char* argv[]) {
  // ...
  result = RunMain(isolate, true);
  // Shut down contexts and collect garbage.
  return result;
}

Shell::RunMain调用Shell::RunMainIsolate

int Shell::RunMain(v8::Isolate* isolate, bool last_run) {
  // ...
  bool success = RunMainIsolate(isolate, keep_context_alive);
  CollectGarbage(isolate);
  // ...
  return (success == Shell::options.expected_to_throw ? 1 : 0);
}

Shell::RunMainIsolate调用SourceGroup::Execute

bool Shell::RunMainIsolate(v8::Isolate* isolate, bool keep_context_alive) {
  // ...
    global_context.Get(isolate)->Enter();
    if (!options.isolate_sources[0].Execute(isolate)) success = false;
    global_context.Get(isolate)->Exit();
  }
  // ...
  return success;
}

SourceGroup::Execute调用Shell::ExecuteString

bool SourceGroup::Execute(Isolate* isolate) {
  bool success = true;

  for (int i = begin_offset_; i < end_offset_; ++i) {
    const char* arg = argv_[i];
    // ...
    HandleScope handle_scope(isolate);
    // 从命令行参数中读取js文件名
    Local<String> file_name =
        String::NewFromUtf8(isolate, arg).ToLocalChecked();
    Local<String> source;
    // 读取文件内容,转化为v8字符串句柄
    if (!Shell::ReadFile(isolate, arg).ToLocal(&source)) {
      printf("Error reading '%s'\n", arg);
      base::OS::ExitProcess(1);
    }
    Shell::set_script_executed();
    Shell::update_script_size(source->Length());
    // 执行js文件
    if (!Shell::ExecuteString(isolate, source, file_name,
                              Shell::kReportExceptions)) {
      success = false;
      break;
    }
  }
  return success;
}

Shell::ExecuteString调用Shell::CompileString进入编译阶段,调用script->Run进入执行阶段

bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
                          Local<String> name,
                          ReportExceptions report_exceptions,
                          Global<Value>* out_result) {
  // ...
  Local<Script> script;
  if (!CompileString<Script>(isolate, context, source, origin)
           .ToLocal(&script)) {
    return false;
  }
  // ...
  MaybeLocal<Value> maybe_result = script->Run(realm);
  return !try_catch.HasCaught();
}

编译

Shell::CompileString经过一系列调用,调用到CompileToplevel

MaybeHandle<SharedFunctionInfo> CompileToplevel(
    ParseInfo* parse_info, Handle<Script> script,
    MaybeDirectHandle<ScopeInfo> maybe_outer_scope_info, Isolate* isolate,
    IsCompiledScope* is_compiled_scope) {
    // 调用ParseProgram初始化parse_info->literal
    if (parse_info->literal() == nullptr &&
      !parsing::ParseProgram(parse_info, script, maybe_outer_scope_info,
                             isolate, parsing::ReportStatisticsMode::kYes)) {
    FailWithException(isolate, script, parse_info,
                      Compiler::ClearExceptionFlag::KEEP_EXCEPTION);
    return MaybeHandle<SharedFunctionInfo>();
  }
  // ...
  Handle<SharedFunctionInfo> shared_info =
      GetOrCreateTopLevelSharedFunctionInfo(parse_info, script, isolate,
                                            is_compiled_scope);
  // Prepare and execute compilation of the outer-most function.
  if (!IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs(
          isolate, script, parse_info, isolate->allocator(), is_compiled_scope,
          &finalize_unoptimized_compilation_data_list, nullptr)) {
    FailWithException(isolate, script, parse_info,
                      Compiler::ClearExceptionFlag::KEEP_EXCEPTION);
    return MaybeHandle<SharedFunctionInfo>();
  }
  // ...
  return shared_info;
}

由源代码生成ast

CompileToplevel先生成了抽象语法树,存储在ParseInfo->literal中。

ParseInfo(解析阶段信息)

ParseInfo 存储 解析 JavaScript 源码生成抽象语法树(AST) 所需的全部信息。

核心字段及作用
字段名类型作用
script_Handle<Script>关联的脚本对象,包含源码内容、文件名、行号等元数据。
literal_FunctionLiteral*解析生成的 函数字面量(AST根节点),描述函数的结构(参数、语句等)。
scope_Scope*当前作用域(全局/函数作用域),用于变量解析和静态作用域分析。
ast_value_factory_AstValueFactory*管理 AST 中字符串、数字等常量的内存分配(如标识符、字面量)。
flags_ParseFlags解析选项标志位(如是否严格模式、是否为箭头函数、是否允许尾调用优化等)。
reusable_state_ParserReusableState*解析器可复用的临时状态(如预解析缓存信息,加速重复解析)。
hash_seed_uint64_t哈希种子,用于确定化变量名哈希值(避免哈希碰撞攻击)。
extension_DeclarationScope*扩展作用域(如模块作用域或 eval 的动态作用域)。
stack_limit_uintptr_t递归解析时的堆栈深度限制,防止堆栈溢出。

ast

打印ast结构的命令如下

d8 --print-ast <filename>.js

ast

1. 函数节点(FUNC at 0)

FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""

at 0:函数的起始位置(字节码偏移或源码位置)。
KIND 0:函数类型标识:
0 表示普通函数(如 function(){})。
• 其他值可能对应箭头函数(KIND 1)、生成器(KIND 2)等。
LITERAL ID 0:函数字面量在编译期的唯一标识符。
SUSPEND COUNT 0:生成器函数的中断次数(非生成器函数为 0)。
NAME "":显式声明的函数名(此处为空,如匿名函数)。
INFERRED NAME "":引擎推断的函数名(如通过 let foo = function(){} 推断为 "foo")。


2. 变量声明(DECLS)

. DECLS
. . VARIABLE (0000028BC18048F0) (mode = CONST, assigned = false) "smiVal"
. . VARIABLE (0000028BC18049B0) (mode = CONST, assigned = false) "heapNumberVal"
. . VARIABLE (0000028BC1804A70) (mode = CONST, assigned = false) "s"
. . VARIABLE (0000028BC1804B30) (mode = CONST, assigned = false) "obj"

VARIABLE:变量声明节点。
(0000028BC18048F0):变量在内存中的唯一标识符(指针地址)。
mode = CONST:变量模式:
CONST:不可重新赋值(对应 const 声明)。
LET:块级作用域变量(对应 let)。
VAR:函数作用域变量(对应 var)。
assigned = false:是否已赋值(初始化表达式不算作赋值)。
"smiVal":变量名。


3. 块节点(BLOCK NOCOMPLETIONS)

. BLOCK NOCOMPLETIONS at -1
. . EXPRESSION STATEMENT at 26

BLOCK:代码块节点,表示一个作用域块。
NOCOMPLETIONS:该块没有完成值(即没有 return 或表达式结果)。
at -1:块的位置信息未明确指定(可能为默认值)。


4. 表达式语句(EXPRESSION STATEMENT)

. . EXPRESSION STATEMENT at 26
. . . kInit at 26
. . . . VAR PROXY context[3] (0000028BC18048F0) (mode = CONST, assigned = false) "smiVal"
. . . . LITERAL 42

EXPRESSION STATEMENT:表达式语句节点。
at 26:对应源码的字符偏移位置(如第 26 个字符处)。
kInit:初始化类型:
kInit:变量初始化(如 const x = 42;)。
• 其他类型:kAssignment(赋值)、kCall(函数调用)等。
VAR PROXY:变量代理节点,指向声明的变量。
context[3]:变量在作用域中的位置(第 3 个上下文槽)。
(0000028BC18048F0):变量标识符(与 DECLS 中的地址对应)。
LITERAL 42:数值字面量(Small Integer,Smi)。


5. 字面量类型

(1) 数值字面量
. . . . LITERAL 42        // Smi(小整数)
. . . . LITERAL 3.14     // HeapNumber(堆分配的浮点数)

LITERAL 42:Smi(小整数),直接存储在指针中。
LITERAL 3.14:HeapNumber,堆内存中存储的浮点数。

(2) 字符串字面量
. . . . LITERAL "V8"     // 字符串常量

LITERAL "V8":字符串常量,存储在常量池或堆中。

(3) 对象字面量
. . . . OBJ LITERAL at 255
. . . . . PROPERTY - CONSTANT
. . . . . . KEY at 257
. . . . . . . LITERAL "x"
. . . . . . VALUE at 260
. . . . . . . LITERAL 1

OBJ LITERAL:对象字面量节点。
PROPERTY - CONSTANT:属性类型为常量(不可变)。
KEY at 257:属性键的位置(字符偏移 257)。
VALUE at 260:属性值的的位置(字符偏移 260)。
LITERAL "x":属性键为字符串 "x"
LITERAL 1:属性值为数值 1


6. 上下文(context)

VAR PROXY context[3]:变量存储在作用域链的第 3 个上下文槽中。
上下文槽分配
context[0]:当前函数的作用域。
context[1]:闭包变量或外层作用域。
context[3]:当前作用域的局部变量。


总结

字段含义
FUNC函数节点,描述函数的类型、名称和作用域。
DECLS变量声明列表,记录变量名、作用域位置和类型。
BLOCK代码块节点,表示一个作用域或语句块。
EXPRESSION STATEMENT表达式语句节点,如变量初始化或赋值。
VAR PROXY变量代理节点,关联变量声明和实际使用。
LITERAL字面量节点,表示常量值(数值、字符串、对象等)。
context[N]变量在作用域链中的存储位置,用于代码生成阶段定位变量。

ast的代码实现

重点关注ParseInfo的literal字段,该字段描述了抽象语法树,初始化的核心流程在Parser::DoParseProgram。

FunctionLiteral* Parser::DoParseProgram(Isolate* isolate, ParseInfo* info) {
  // ...
  ParseStatementList(&body, Token::kEos);
  // ...
  int parameter_count = 0;
  result = factory()->NewScriptOrEvalFunctionLiteral(
      scope, body, function_state.expected_property_count(), parameter_count);
  result->set_suspend_count(function_state.suspend_count());
  // ...
  return result;
}

ParseStatementList会解析顶级作用域里的所有语句,构建astNode,astNode会记录语句在源码的起始位置以及长度,astNode的派生类会记录不同语句的额外信息。
至于需要构建什么语句,则需要根据下一个token来决定。核心逻辑在ParserBase::ParseStatementListItem

template <typename Impl>
typename ParserBase<Impl>::StatementT
ParserBase<Impl>::ParseStatementListItem() {

  switch (peek()) {
    case Token::kFunction:
      return ParseHoistableDeclaration(nullptr, false);
    case Token::kClass:
      Consume(Token::kClass);
      return ParseClassDeclaration(nullptr, false);
    case Token::kVar:
    case Token::kConst:
      return ParseVariableStatement(kStatementListItem, nullptr);
    case Token::kLet:
      if (IsNextLetKeyword()) {
        return ParseVariableStatement(kStatementListItem, nullptr);
      }
      break;
    case Token::kUsing:
      if (!v8_flags.js_explicit_resource_management) break;
      if (!is_using_allowed()) break;
      if (!(scanner()->HasLineTerminatorAfterNext()) &&
          Token::IsAnyIdentifier(PeekAhead())) {
        return ParseVariableStatement(kStatementListItem, nullptr);
      }
      break;
    case Token::kAwait:
      if (!v8_flags.js_explicit_resource_management) break;
      if (!is_await_allowed()) break;
      if (!is_using_allowed()) break;
      if (!(scanner()->HasLineTerminatorAfterNext()) &&
          PeekAhead() == Token::kUsing &&
          !(scanner()->HasLineTerminatorAfterNextNext()) &&
          Token::IsAnyIdentifier(PeekAheadAhead())) {
        return ParseVariableStatement(kStatementListItem, nullptr);
      }
      break;
    case Token::kAsync:
      if (PeekAhead() == Token::kFunction &&
          !scanner()->HasLineTerminatorAfterNext()) {
        Consume(Token::kAsync);
        return ParseAsyncFunctionDeclaration(nullptr, false);
      }
      break;
    default:
      break;
  }
  return ParseStatement(nullptr, nullptr, kAllowLabelledFunctionStatement);
}

然后,CompileToplevel生成了CompileToplevel。

在 V8 引擎中,Handle<SharedFunctionInfo> shared_info 的作用是 管理顶层代码的编译元信息,具体如下:


1. SharedFunctionInfo 的核心作用

存储函数元信息
SharedFunctionInfo(SFI)是 V8 内部用于存储 函数元信息 的核心数据结构,包括:
• 源代码位置(起点、长度)。
• 函数名、参数数量、预期作用域。
• 编译后的代码指针(Code 对象)。
• 优化状态(是否经过 TurboFan 优化)。

共享与复用
SFI 是 跨执行上下文共享 的,即使多次执行同一函数,也只需编译一次,提升性能。


2. GetOrCreateTopLevelSharedFunctionInfo 的作用

此函数用于 获取或创建顶层代码的 SFI,具体逻辑如下:

  1. 检查是否已存在
    如果脚本已编译过,直接返回现有的 SFI(避免重复编译)。
  2. 创建新的 SFI
    如果不存在,初始化新的 SFI 并关联到当前脚本。
  3. 关联编译信息
    将解析阶段生成的元信息(如 AST、作用域链)绑定到 SFI。

3. shared_info 在编译流程中的角色

编译入口
SFI 是编译过程的起点,V8 通过它获取代码的元信息,并生成可执行的字节码或机器码。
代码缓存
编译后的代码(如 Ignition 字节码或 TurboFan 优化代码)存储在 SFI 中,后续执行直接复用。
优化触发
SFI 跟踪函数的热度(执行频率),当达到阈值时触发 TurboFan 优化。


接着,CompileToplevel调用IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs。

template <typename IsolateT>
bool IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs(
    IsolateT* isolate, Handle<Script> script, ParseInfo* parse_info,
    AccountingAllocator* allocator, IsCompiledScope* is_compiled_scope,
    FinalizeUnoptimizedCompilationDataList*
        finalize_unoptimized_compilation_data_list,
    DeferredFinalizationJobDataList*
        jobs_to_retry_finalization_on_main_thread) {
  // ...
    std::unique_ptr<UnoptimizedCompilationJob> job =
        ExecuteSingleUnoptimizedCompilationJob(parse_info, literal, script,
                                               allocator, &functions_to_compile,
                                               isolate->AsLocalIsolate());

    // ...
  return compilation_succeeded;
}

由ast生成字节码

打印字节码指令

d8 --print-bytecode <filename>.js

执行结果如下
字节码

Frame

栈帧用于存储函数的局部变量、参数和临时数据。
若函数有多个局部变量或复杂表达式,栈帧大小会相应增加。

000001EA00140048 @ 0 : 0d 2a LdaSmi [42]

  • ​地址 000001EA00140048:
    当前字节码指令在内存中的地址(具体值因运行环境而异)。

  • ​@ 0:
    指令在字节码数组中的 ​偏移量(offset) 为 0,表示这是函数的第一条指令。

  • 操作码 0d 2a:
    0d 是操作码(opcode)的十六进制值,对应 LdaSmi 指令。
    2a 是操作数(operand)的十六进制值,对应十进制值 42。

  • 指令 LdaSmi [42]:
    ​作用:将小整数(Smi)42 加载到累加器(Accumulator)。

Constant pool

除了smi外,其他常量需要被分配堆对象。

TrustedFixedArray:
V8 内部用于存储 ​不可变数据
的数组结构,通常用于常量池或元数据存储。

Map 对象(也称为 ​Hidden Class)
是 V8 内部用于描述 ​对象内存布局 的元数据,包含以下信息:

  • 对象类型(如 TRUSTED_FIXED_ARRAY_TYPE)。
  • 属性数量和存储偏移量。
  • 元素类型(如 Smi、HeapObject 等)。
  • 继承关系和原型链。

​作用:

  • 优化属性访问速度(通过预计算偏移量)。
  • 支持动态类型追踪(Type Feedback),为 JIT 编译提供信息。

Handler Table

​作用:存储 ​异常处理逻辑​(如 try/catch 的跳转地址)。
​当前状态:size=0 表示当前代码无异常处理逻辑。

Source Position Table (size = 0)

​作用:映射 ​字节码偏移量 到 ​源代码行号,用于调试和错误堆栈。
​当前状态:size=0 可能表示未启用调试符号生成或代码无复杂控制流。


具体实现

IterativelyExecuteAndFinalizeUnoptimizedCompilationJobs经过一系列调用,会调用到GenerateBodyStatementsWithoutImplicitFinalReturn。

void BytecodeGenerator::GenerateBodyStatementsWithoutImplicitFinalReturn(
    int start) {
  ZonePtrList<Statement>* body = info()->literal()->body();
  // ...
  VisitStatements(body, start);
}

info()->literal()->body()获取到的就是之前生成的astNode,VisitStatements会遍历每个statement并进行访问,并根据statement的类型生成不同的字节码。
部分访问路径

在 V8 引擎中,JavaScript 代码被解析为 抽象语法树(AST),所有语法结构均以不同类型的 Statement(语句)和 Expression(表达式)节点表示。以下是 deepseek给出的V8 中常见的 StatementExpression 类型,最新类型请以ast/ast.h为准。


一、Statement(语句)

语句表示代码的执行单元,通常不直接返回值,而是控制程序流程或执行操作。

1. 基本语句
类型作用示例
BlockStatement代码块,包含多个语句(用 {} 包裹){ let x = 1; console.log(x); }
EmptyStatement空语句;(仅分号)
ExpressionStatement表达式语句,将表达式作为语句执行x = 1;foo();
DebuggerStatement调试语句(触发调试器断点)debugger;
2. 变量与声明
类型作用示例
VariableDeclaration变量声明(var/let/constconst x = 1;
FunctionDeclaration函数声明function foo() {}
ClassDeclaration类声明class Bar {}
3. 控制流语句
类型作用示例
IfStatement条件语句(if/elseif (x > 0) { ... } else { ... }
SwitchStatement多分支条件语句switch (x) { case 1: ... }
ForStatementfor 循环for (let i=0; i<10; i++) { ... }
ForInStatementfor...in 循环for (let key in obj) { ... }
ForOfStatementfor...of 循环(遍历可迭代对象)for (let item of arr) { ... }
WhileStatementwhile 循环while (x > 0) { ... }
DoWhileStatementdo...while 循环do { ... } while (x > 0);
BreakStatement跳出循环或 switchbreak;
ContinueStatement跳过当前循环迭代continue;
4. 函数与模块
类型作用示例
ReturnStatement函数返回语句return x;
ThrowStatement抛出异常throw new Error();
TryStatementtry/catch/finally 异常处理try { ... } catch (e) { ... }
ImportDeclaration导入模块(ES6 importimport { x } from 'mod';
ExportDeclaration导出模块(ES6 exportexport const x = 1;

二、Expression(表达式)

表达式会返回一个值,可以作为其他表达式或语句的一部分。

1. 基本表达式
类型作用示例
Literal字面量(数值、字符串、布尔值、nullundefined42, "hello", true, null
Identifier标识符(变量名、属性名)x, console.log
ThisExpressionthis 关键字this.value
Supersuper 关键字(类继承中访问父类方法)super.method()
2. 对象与数组
类型作用示例
ObjectExpression对象字面量{ x: 1, y: 2 }
ArrayExpression数组字面量[1, 2, 3]
SpreadElement展开运算符(...[...arr, 4]
3. 运算表达式
类型作用示例
AssignmentExpression赋值操作(=+= 等)x = 1x += 2
BinaryExpression二元运算(算术、逻辑、比较等)x + y, a > b, x && y
UnaryExpression一元运算(!-typeof 等)-x, typeof x, delete obj.x
UpdateExpression自增/自减(++--x++--y
LogicalExpression逻辑运算(&&、`
4. 函数与调用
类型作用示例
CallExpression函数调用foo(), obj.method()
NewExpressionnew 运算符创建实例new MyClass()
ArrowFunctionExpression箭头函数(x) => x + 1
FunctionExpression函数表达式function () {}
5. 高级表达式
类型作用示例
MemberExpression访问对象属性或方法obj.xarr[0]
ConditionalExpression三元条件运算符(? :x > 0 ? 'yes' : 'no'
TemplateLiteral模板字符串(反引号包裹)`Value: ${x}`
TaggedTemplateExpression标签模板字符串tag`Hello ${name}`
YieldExpressionyield 关键字(生成器函数)yield 1;
AwaitExpressionawait 关键字(异步函数)await fetch(url);

三、V8 内部优化

在 V8 的编译过程中,AST 节点会被转换为 字节码 或直接优化为 机器码。以下是一些关键优化点:

  1. 隐藏类(Hidden Class):对象字面量(ObjectExpression)会生成隐藏类以加速属性访问。
  2. 内联缓存(Inline Cache):高频调用的 CallExpression(如 obj.method())会被缓存方法地址。
  3. 类型反馈(Type Feedback)BinaryExpressionAssignmentExpression 会收集类型信息以优化运算。

四、示例代码的 AST 映射

对于以下代码:

function sum(a, b) {
  return a + b;
}
const result = sum(1, 2);

其 AST 结构为:
FunctionDeclarationsum 函数)
Identifierab 参数)
BlockStatement(函数体)
ReturnStatement
BinaryExpressiona + b
VariableDeclarationconst result
CallExpressionsum(1, 2)
Literal12


对于字面量而言,还需要区分smi和其他字面量,因为smi不需要被加入常量池,这段处理逻辑在LoadLiteral。

BytecodeArrayBuilder& BytecodeArrayBuilder::LoadLiteral(double value) {
  // If we can encode the value as a Smi, we should.
  int smi;
  if (DoubleToSmiInteger(value, &smi)) {
    LoadLiteral(Smi::FromInt(smi));
  } else {
    size_t entry = GetConstantPoolEntry(value);
    OutputLdaConstant(entry);
  }
  return *this;
}

DoubleToSmiInteger的逻辑就是调用IsSmiDouble判断是否为smi,如果是就将其由double类型转为Int类型。
IsSmiDouble就是按value是否在smi大小范围内来判断的。

bool IsSmiDouble(double value) {
  return value >= Smi::kMinValue && value <= Smi::kMaxValue &&
         !IsMinusZero(value) && value == FastI2D(FastD2I(value));
}

GetConstantPoolEntry方法会把字面量加入常量池,核心逻辑在 ConstantArrayBuilder::ConstantArraySlice::Allocate。

size_t ConstantArrayBuilder::ConstantArraySlice::Allocate(
    ConstantArrayBuilder::Entry entry, size_t count) {
  DCHECK_GE(available(), count);
  size_t index = constants_.size();
  DCHECK_LT(index, capacity());
  for (size_t i = 0; i < count; ++i) {
    // constants_用来记录哪些字面量需要加入常量池
    constants_.push_back(entry);
  }
  return index + start_index();
}

字面量在constants_中的下标也会被放入heap_xx_map,heap_xx_map会在预分配堆对象阶段使用。

size_t ConstantArrayBuilder::Insert(double number) {
  if (std::isnan(number)) return InsertNaN();
  auto entry = heap_number_map_.find(number);
  if (entry == heap_number_map_.end()) {
    index_t index = static_cast<index_t>(AllocateIndex(Entry(number)));
    heap_number_map_[number] = index;
    return index;
  }
  return entry->second;
}

对于object对象,会调用AllocateDeferredConstantPoolEntry将模板加入常量池。

Handle<BytecodeArray> BytecodeGenerator::FinalizeBytecode(
    IsolateT* isolate, Handle<Script> script) {
  // ...
  Handle<BytecodeArray> bytecode_array = builder()->ToBytecodeArray(isolate);
  // ...
  return bytecode_array;
}

当执行const converted = Number(smiVal)时,该表达式会被识别为GlobalCall,由于Number被注册为js关键字,所以会调用LoadGlobal函数。LoadGlobal最终也会调用到Allocate函数,将Number这个函数名作为字符串加入常量池。

void BytecodeGenerator::BuildVariableLoad(Variable* variable,
                                          HoleCheckMode hole_check_mode,
                                          TypeofMode typeof_mode) {
  switch (variable->location()) {
    // ...
    case VariableLocation::UNALLOCATED: {
      if (variable->raw_name() == ast_string_constants()->undefined_string()) {
        builder()->LoadUndefined();
      } else {
        FeedbackSlot slot = GetCachedLoadGlobalICSlot(typeof_mode, variable);
        builder()->LoadGlobal(variable->raw_name(), feedback_index(slot),
                              typeof_mode);
      }
      break;
    }
    // ...
  }
}

最终ToBytecodeArray方法存储生成的字节码,该方法会调用ToFixedArray方法创建常量池。

Handle<BytecodeArray> BytecodeArrayWriter::ToBytecodeArray(
  // ...
  int bytecode_size = static_cast<int>(bytecodes()->size());
  // 栈帧长度 = 需要保存的寄存器数量 * 指针长度
  int frame_size = register_count * kSystemPointerSize;
  // 初始化常量池,预分配堆变量可以提升执行效率
  DirectHandle<TrustedFixedArray> constant_pool =
      constant_array_builder()->ToFixedArray(isolate);
  Handle<BytecodeArray> bytecode_array = isolate->factory()->NewBytecodeArray(
      bytecode_size, &bytecodes()->front(), frame_size, parameter_count,
      max_arguments, constant_pool, handler_table);
  return bytecode_array;
}

ToFixedArray方法会便利constants_,调用ToHandle方法,ToHandle方法根据constant的tag_属性,创建不同类型的堆变量。

template <typename IsolateT>
Handle<Object> ConstantArrayBuilder::Entry::ToHandle(IsolateT* isolate) const {
  switch (tag_) {
    case Tag::kDeferred:
      // We shouldn't have any deferred entries by now.
      UNREACHABLE();
    case Tag::kHandle:
      return handle_;
    case Tag::kSmi:
    case Tag::kJumpTableSmi:
      return handle(smi_, isolate);
    case Tag::kUninitializedJumpTableSmi:
      // TODO(leszeks): There's probably a better value we could use here.
      return isolate->factory()->the_hole_value();
    case Tag::kRawString:
      return raw_string_->string();
    case Tag::kConsString:
      return cons_string_->AllocateFlat(isolate);
    case Tag::kHeapNumber:
      return isolate->factory()->template NewNumber<AllocationType::kOld>(
          heap_number_);
    case Tag::kBigInt:
      // This should never fail: the parser will never create a BigInt
      // literal that cannot be allocated.
      return BigIntLiteral(isolate, bigint_.c_str()).ToHandleChecked();
    case Tag::kScope:
      return scope_->scope_info();
#define ENTRY_LOOKUP(Name, name) \
  case Tag::k##Name:             \
    return isolate->factory()->name();
      SINGLETON_CONSTANT_ENTRY_TYPES(ENTRY_LOOKUP);
#undef ENTRY_LOOKUP
  }
  UNREACHABLE();
}

自此,编译阶段结束,进入执行阶段。

执行

我们在js文件中调用的%DebugPrint函数最终会调用到DebugPrintImpl。该函数先获取到smi或者堆对象,打印出来,如果是堆对象还会额外打印map信息。

static void DebugPrintImpl(Tagged<MaybeObject> maybe_object, std::ostream& os) {
  // ...
    Tagged<Object> object = maybe_object.GetHeapObjectOrSmi();
    os << "DebugPrint: ";
    Print(object, os);
    if (IsHeapObject(object)) {
      Print(Cast<HeapObject>(object)->map(), os);
    }
  // ...
  os << std::endl;
}

根据指针最末尾是否为0来判断是否是smi,如果是,就直接返回指针(因为smi值存在指针中),如果不是,则获取堆对象地址(即指针左移一位再-1)

Tagged<Object> TaggedImpl<kRefType, StorageType>::GetHeapObjectOrSmi() const {
  CHECK(kIsFull);
  if (IsSmi()) {
    return Tagged<Object>(ptr_);
  }
  return GetHeapObject();
}
constexpr bool IsSmi() const { return HAS_SMI_TAG(ptr_); }
#define HAS_SMI_TAG(value) \
  ((static_cast<i::Tagged_t>(value) & ::i::kSmiTagMask) == ::i::kSmiTag)
template <HeapObjectReferenceType kRefType, typename StorageType>
Tagged<HeapObject> TaggedImpl<kRefType, StorageType>::GetHeapObject() const {
  // ...
    return Cast<HeapObject>(Tagged<Object>(ptr_));
}

打印的具体实现。
Smi::ToInt会将指针右移一位,还原成存储的真实值

void Print(Tagged<Object> obj, std::ostream& os) {
  if (IsSmi(obj)) {
    os << "Smi: " << std::hex << "0x" << Smi::ToInt(obj);
    os << std::dec << " (" << Smi::ToInt(obj) << ")\n";
  } else {
    Cast<HeapObject>(obj)->HeapObjectPrint(os);
  }
}
void HeapNumber::HeapNumberPrint(std::ostream& os) {
  PrintHeader(os, "HeapNumber");
  os << "\n - value: ";
  HeapNumberShortPrint(os);
  os << "\n";
}
void HeapObject::PrintHeader(std::ostream& os, const char* id) {
  PrintHeapObjectHeaderWithoutMap(*this, os, id);
  // 获取堆的基地址,堆对象真实地址 = 基地址 + 压缩指针地址
  PtrComprCageBase cage_base = GetPtrComprCageBase();
  if (!SafeEquals(GetReadOnlyRoots().meta_map())) {
  
    os << "\n - map: " << Brief(map(cage_base));
  }
}
void PrintHeapObjectHeaderWithoutMap(Tagged<HeapObject> object,
                                     std::ostream& os, const char* id) {
  os << reinterpret_cast<void*>(object.ptr()) << ": [";
  os << id;
  os << "]";
}
void HeapNumber::HeapNumberShortPrint(std::ostream& os) {
  double val = value();
  if (i::IsMinusZero(val)) {
    os << "-0.0";
  } else if (val == DoubleToInteger(val) && val >= kMinSafeInteger &&
             val <= kMaxSafeInteger) {
    // Print integer HeapNumbers in safe integer range with max precision: as
    // 9007199254740991.0 instead of 9.0072e+15
    int64_t i = static_cast<int64_t>(val);
    os << i << ".0";
  } else {
    os << val;
  }
}

对于Number()构造函数而言,如果传入的字面量是smi,那么Number函数会直接返回Smi对象,否则返回堆对象。
相关类型转换可以在convert.tq中找到。Torque 是 ​V8 引擎团队专门设计的一种领域特定语言(Domain-Specific Language, DSL)​,用于高效、安全地实现 V8 的 ​内置函数(Builtins)​ 和 ​底层运行时逻辑。它的核心目标是简化 V8 内部代码的开发和维护,同时生成高性能的机器代码。torque入门指南

Convert<Number, int32>(i: int32): Number {
  return ChangeInt32ToTagged(i);
}
TNode<Number> CodeStubAssembler::ChangeInt32ToTagged(TNode<Int32T> value) {
  if (SmiValuesAre32Bits()) {
    return SmiTag(ChangeInt32ToIntPtr(value));
  }
  // ...
  {
    TNode<Float64T> value64 = ChangeInt32ToFloat64(value);
    TNode<HeapNumber> result = AllocateHeapNumberWithValue(value64);
    var_result = result;
    Goto(&if_join);
  }
  // ...
  return var_result.value();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值