OpenHarmony ArkTS 运行时深度分析

OpenHarmony ArkTS 运行时(ets_runtime)深度分析

基于 arkcompiler/ets_runtime/arkcompiler/runtime_core/ 源码

目录

术语

缩写 全称 说明
ABC Ark Byte Code ArkTS 编译生成的字节码文件 (.abc)
AN Ark Native AOT 编译生成的机器码文件 (.an)
AOT Ahead-of-Time 编译时预编译为机器码
JIT Just-in-Time 运行时热点代码编译
VM Virtual Machine 虚拟机(每个 UIAbility 一个 EcmaVM)
GC Garbage Collection 垃圾回收
CMS Concurrent Mark Sweep 并发标记清除 GC
CMC Concurrent Mark Compact 并发标记紧凑 GC
IC Inline Cache 内联缓存优化
NAPI Native API C++ 原生接口桥接
Snapshot VM 堆快照(加速启动)

1. 概述

ArkTS 运行时(ets_runtime)是 OpenHarmony 上执行 ArkTS/TypeScript/JavaScript 代码的默认运行时。它位于 arkcompiler/ets_runtime/,实现 ECMAScript 2021 标准(严格模式),为每个应用进程提供一个独立的 EcmaVM 实例。

运行时不是系统启动时一次性初始化的,而是按需、按进程创建——每个 UIAbility 对应一个 EcmaVM。

在设计上的核心追求:

性能:AOT + JIT + IC → 接近原生
启动:Snapshot 快照 → 跳过标准库初始化
安全:严格模式 + 无 eval + 无动态函数创建
并发:Actor 模型 TaskPool → 自动扩缩 Worker

2. 运行时架构

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐
│                    ArkTS 应用 (HAP)                         │
│              .abc 字节码文件 (Ark Byte Code)                 │
├─────────────────────────────────────────────────────────────┤
│            ets_runtime (libark_jsruntime.so)                │
│                                                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
│  │Interpreter│  │   JIT    │  │   AOT    │  │  Inline    │  │
│  │ (解释器)  │  │ (即时编译)│  │ (预编译) │  │  Cache    │  │
│  └──────────┘  └──────────┘  └──────────┘  └────────────┘  │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Memory Management (内存管理)                        │  │
│  │  ┌──────────┐  ┌──────────┐  ┌───────────────────┐  │  │
│  │  │  Heap    │  │ CMS-GC   │  │ CMC-GC / Compact  │  │  │
│  │  │ (对象堆) │  │ (并发回收)│  │ (并发紧凑)        │  │  │
│  │  └──────────┘  └──────────┘  └───────────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌──────────┐  ┌──────────────┐  ┌────────────────────┐   │
│  │  Module  │  │   TaskPool   │  │  Snapshot (快照)    │   │
│  │ (ES模块) │  │ (Actor并发)  │  │ 序列化/反序列化     │   │
│  └──────────┘  └──────────────┘  └────────────────────┘   │
│                                                             │
│  ┌──────────┐  ┌──────────────┐  ┌────────────────────┐   │
│  │  NAPI    │  │   Builtins   │  │  TypeSystem        │   │
│  │(C++桥接) │  │ (标准库)     │  │ (TS 类型系统)     │   │
│  └──────────┘  └──────────────┘  └────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│          runtime_core (libarkcompiler_core.z.so)            │
│   libpandafile / libpandabase / 字节码格式 / 通用基础设施   │
├─────────────────────────────────────────────────────────────┤
│                ACE 框架 (foundation/arkui)                  │
│              UIContent / 组件树 / 渲染管线                   │
└─────────────────────────────────────────────────────────────┘

2.2 两个运行时层

产物 职责
runtime_core arkcompiler/runtime_core/ libarkcompiler_core.z.so 字节码文件格式(ABC)、汇编器、反汇编器、通用工具
ets_runtime arkcompiler/ets_runtime/ libark_jsruntime.so ECMAScript 运行时:VM、解释器、JIT、GC、NAPI

runtime_core 是语言无关的基础设施,ets_runtime 是 ECMAScript 语言的具体实现。

2.3 关键目录

arkcompiler/ets_runtime/ecmascript/
├── base/              # 基础工具类
├── builtins/          # ECMAScript 标准库(Array/Object/Function 等)
├── interpreter/       # 字节码解释器
├── jit/               # 即时编译器
├── compiler/          # AOT 编译器 + 类型推断
├── mem/               # GC + 堆管理
├── module/            # ES Module 加载器
├── snapshot/          # VM 堆快照序列化/反序列化
├── napi/              # C++ 原生 API 桥接
├── jspandafile/       # .abc 文件管理(加载/解析/缓存)
├── ic/                # 内联缓存(Inline Cache)
├── taskpool/          # Actor 并发任务池
├── ohos/              # OHOS 系统集成(AOT 列表/快照配置)
└── js_vm/             # CLI 工具(ark_js_vm,调试用)

3. 初始化时机与链路

ArkTS 运行时不在系统启动时集中初始化。它遵循"一个进程一个 VM"的原则。

3.1 初始化时序

用户点击桌面图标 → Launcher → AMS::StartAbility
    │
    ▼
AppSpawn 孵化应用进程
    │
    ▼
AbilityMain → ACE::UIContent::Create()
    │
    ├── 1. EcmaVM::Create()
    │   ├── Heap::Create()          → 分配 GC 堆
    │   ├── Snapshot::Deserialize() → 加载 VM 快照(加速启动)
    │   ├── Builtins::Initialize()  → 初始化 ECMAScript 标准库
    │   ├── ModuleManager::Init()   → 初始化模块加载器
    │   └── JSPandaFileManager::Init() → 字节码文件管理器
    │
    ├── 2. 加载 .abc 字节码
    │   └── JSPandaFileManager::LoadJSPandaFile(hap_path)
    │
    ├── 3. Module::Instantiate() + Evaluate()
    │
    └── 4. 开始执行 → Interpreter / AOT / JIT

3.2 多 VM 实例

每个 UIAbility 拥有独立的 EcmaVM,共享数据通过 SharedModuleManagerSharedObject 机制跨 VM 传递。TaskPool 中的 Worker 线程拥有自己的隔离 VM,不能直接访问主线程的堆。

3.3 系统级 vs 应用级初始化

场景 初始化次数 初始化内容
系统服务(SystemUI、Launcher) 进程启动时 EcmaVM + Builtins 堆快照 + ModuleSnapshot + JSPandaFile 快照
三方应用 每次启动 EcmaVM + Builtins 堆快照 + ModuleSnapshot + 加载 .abc
TaskPool Worker 按需创建 隔离 VM + Builtins 堆快照 + 共享 ModuleSnapshot(从主 VM 反序列化,跳过 Module 重新解析)

这里涉及两种不同的快照:

  • Builtins 堆快照(VM heap snapshot):序列化 ECMAScript 标准库对象(Array、Object 等原型),加速 EcmaVM::Create()
  • ModuleSnapshot:序列化 ES Module 的解析结果(模块记录、导出名表、依赖图),加速新 VM 的 Module::Resolve()
  • JSPandaFileSnapshot:序列化 .abc 文件加载后的解析结果(MethodLiteral 索引),加速新 VM 的字节码加载

Worker 线程启动时,从主 VM 共享 ModuleSnapshot + JSPandaFileSnapshot,不需要重新解析 import 依赖树和重读 .abc 文件。

4. 在系统镜像中的位置

4.1 编译产物分布

out/{product}/packages/phone/system/
├── lib64/
│   ├── libark_jsruntime.so           ← ets_runtime 主运行时
│   ├── libarkcompiler_core.z.so      ← runtime_core 基础设施
│   ├── libark_jit.so                 ← JIT 编译器
│   ├── libark_aot_compiler_service   ← AOT 编译器服务
│   └── libark_*.so                   ← 其他运行时库
│
├── etc/ark/
│   ├── app_startup_snapshot.json     ← 快照配置文件
│   ├── app_aot_jit_enable_list.conf  ← AOT/JIT 白名单
│   └── enable_aot_list.conf          ← AOT 启用的应用列表
│
└── app/                              ← HAP 内含 .abc
    └── com.example.app/
        └── entry.hap → 解压后含 xxx.abc

4.2 部件定义

// arkcompiler/ets_runtime/bundle.json
{
  "component": {
    "name": "ets_runtime",
    "subsystem": "arkcompiler",
    "adapted_system_type": ["standard"],
    "deps": {
      "components": ["runtime_core", "napi", "hilog", "hitrace", "icu", "zlib"]
    },
    "build": {
      "sub_component": [
        "//arkcompiler/ets_runtime:ark_js_packages"
      ]
    }
  }
}

编译目标 ark_js_packagesBUILD.gn 定义,产出包含 libark_jsruntime.so 及其依赖。

5. 应用加载与执行流程

5.1 从 .ts 到 .abc 到执行

开发者编写 .ets / .ts
    │
    ▼  es2abc (arkcompiler/ets_frontend)
    │
.abc 字节码文件
    │
    ▼  打包到 HAP → system/app/{bundle}/entry.hap
    │
    ▼  hdc install 安装
    │
    ▼  运行时加载:
    │
    ├── 1. JSPandaFileManager::LoadJSPandaFile(abc_path)
    │   │   (ecmascript/jspandafile/js_pandafile_manager.cpp)
    │   ├── 打开 .abc 二进制文件
    │   ├── 解析 panda_file::File 头(magic number, version, checksum)
    │   ├── 提取 Program 对象(含入口函数引用)
    │   ├── 加载 ConstantPool(字符串、方法字面量、类字面量)
    │   └── 注册到 JSPandaFile 缓存
    │
    ├── 2. TranslateClassesTask (ecmascript/jspandafile/...)
    │   └── 多线程翻译类定义:从字节码 → 运行时 Class 对象
    │
    ├── 3. ModuleManager::GetImportedModule()
    │   ├── ModuleResolver::Resolve() — 递归解析所有 import
    │   ├── SourceTextModule::Instantiate() — 链接 import/export
    │   └── SourceTextModule::Evaluate() — 执行模块顶层代码
    │
    └── 4. 执行入口函数
        └── Interpreter::Execute(entry_method)

5.2 JSPandaFile 文件加载

JSPandaFile(ecmascript/jspandafile/js_pandafile.h)是对 .abc 文件的管理封装:

JSPandaFile
  ├── Program*              ← 节目对象(入口函数引用)
  ├── ConstantPool*         ← 常量池(字符串/方法/类字面量)
  ├── MethodLiteral[]       ← 方法字面量索引
  ├── JSRecordInfo          ← 模块元数据
  └── CreateMode            ← RUNTIME / DFX

.abc 文件的二进制格式定义在 runtime_core/libpandafile/ 中,包含:

  • magic number(文件头标识)
  • classes 段(类定义)
  • methods 段(方法字节码)
  • strings 段(字符串常量)
  • literals 段(字面量数组)

5.3 ES Module 加载与执行三阶段

ES Module 规范定义了模块从解析到执行的三阶段(ecmascript/module/js_module_manager.h):

阶段 操作 状态
Parse (解析) 从 .abc 提取 SourceTextModule UNINSTANTIATED
Instantiate (实例化) 创建 ModuleNamespace,链接导入/导出 INSTANTIATED
Evaluate (求值) 执行模块体代码,填充导出 EVALUATED
ModuleStatus 枚举(ecmascript/module/):
UNINSTANTIATED → INSTANTIATING → INSTANTIATED → 
EVALUATING → EVALUATING_ASYNC → EVALUATED → ERRORED

关联的序列化模块: ecmascript/serializer/module_serializer

6. 编译时语法检查

ArkTS 编译(es2abc/ets2panda)过程中的语法/语义检查分布在四个阶段。前三个阶段在 C++ 编译器内完成,第四阶段是独立的 TypeScript Linter。

6.1 四个检查阶段

源码 .ets / .ts
    │
    ├── [1] Parser(语法解析)
    │   位置:ets_frontend/ets2panda/parser/
    │   规则:C++ 硬编码,无独立规则文件
    │   检查:括号匹配、关键字、语句结构等基础语法
    │
    ├── [2] AST Verifier(结构校验)
    │   位置:ets_frontend/ets2panda/ast_verifier/invariants/
    │   规则:每个规则一个 .cpp 文件(20+ 条)
    │   检查:AST 结构完整性——节点父子关系、作用域声明等
    │
    ├── [3] Checker(类型检查 + 语义分析)
    │   位置:ets_frontend/ets2panda/checker/ets/
    │   规则:~30 个 C++ 文件,使用 TypeRelation API
    │   检查:类型兼容性、可达性、装箱/拆箱、重载选择
    │   核心约束:只设置元数据,不修改 AST
    │
    └── [4] Linter(静态分析,可选 --arkts-2)
        位置:ets_frontend/ets2panda/linter/
        规则:TypeScript 实现,docs/rules/*.md(40+ 条)
        检查:ArkTS-Static 合规性(强制类型标注、禁止动态代码、并发安全等)

6.2 AST Verifier 规则(阶段 2)

每个规则是一个独立的 C++ invariant,位于 ast_verifier/invariants/

规则文件 检查内容
arithmeticOperationValid.cpp 算术操作操作数类型兼容
checkAbstractMethod.cpp 抽象方法必须在抽象类中
checkConstProperties.cpp const 属性不可被重新赋值
checkScopeDeclaration.cpp 作用域内声明合法
checkStructDeclaration.cpp struct 声明格式正确
everyChildHasValidParent.cpp 每个 AST 节点有合法父节点
everyChildInParentRange.cpp 子节点范围在父节点内
forLoopCorrectlyInitialized.cpp for 循环初始化表达式合法
getterSetterValidation.cpp getter/setter 成对出现且类型匹配
identifierHasVariable.cpp 标识符已绑定到变量声明

这些规则在 debug 构建中自动运行(--ast-verifier:phases=each),且不允许被禁用——AGENTS.md 有硬性约束。

6.3 Checker 类型检查规则(阶段 3)

Checker 是 ETS 编译器最核心的检查阶段。规则分布在 checker/ets/ 下:

// checker/ets/ 中的规则文件及其职责
├── function.cpp              // 函数调用、重载选择、签名匹配
├── arithmetic.cpp            // 算术表达式类型推断和检查
├── assignAnalyzer.cpp        // 赋值兼容性分析(左值/右值)
├── aliveAnalyzer.cpp         // 活性/可达性分析(死代码检测)
├── boxingConverter.cpp       // 自动装箱(int → Integer)
├── unboxingConverter.cpp     // 自动拆箱(Integer → int)
├── wideningConverter.cpp     // 类型加宽转换(int → long)
├── typeConverter.cpp         // 类型转换规则
├── castingContext.cpp        // 显式转型合法性
├── typeRelationContext.cpp   // 子类型/同一性判断
├── etsWarningAnalyzer.cpp   // ETS 特定警告
└── validateHelpers.cpp       // 校验辅助函数

Checker 使用 TypeRelation API 判断类型关系,而非硬编码类型名:

// 正确做法:使用 TypeRelation API
if (TypeRelation::IsSupertypeOf(targetType, sourceType)) {
    // 兼容,可以赋值
} else {
    // 报错:Type 'X' is not assignable to type 'Y'
}

Checker 的核心设计约束:

Checker 只设置类型/变量元数据,不允许修改 AST 结构(添加/删除/替换节点)。结构转换属于 lowering 阶段,Checker 不参与。

这是 ArkTS 编译器与 TypeScript 编译器的关键区别——TypeScript 的检查器会推断类型并写入 AST,ArkTS 的检查器更保守。

6.4 Linter 规则(阶段 4)

Linter 是用 TypeScript 编写的独立静态分析工具,用于 ArkTS-Dynamic → ArkTS-Static 迁移。规则文档在 linter/docs/rules/recipe*.md

类别 规则示例 规则文件
强制类型标注 禁止隐式 any recipe1.md
禁止动态代码 禁止 evalnew Function recipe113.md
并发安全 TaskPool 对象传递限制 recipe137.md
废弃 API 检测已弃用的 SDK API homecheck/
代码规范 const/readonly 强制 recipe134.md
内存安全 Sendable 对象约束 recipe136.md

配置在 linter/rule-config.json 中按类别组织:

{
  "arkts2": { /* ArkTS-Static 强制规则 */ },
  "concurrent": { /* 并发安全检查 */ },
  "migrate": { /* 迁移辅助规则 */ },
  "homecheck": { /* 运行时错误检查 */ }
}

调用方式(独立于编译管线):

# 编译时(es2abc/ets2panda)自动运行前三阶段
# Linter 通过 DevEco Studio 或命令行手动触发
node dist/tslinter.js --arkts-2 --autofix input.ets

6.5 四阶段关系总结

阶段 语言 是否强制 出错时
Parser C++ 编译直接失败
AST Verifier C++ 是(debug 构建) 编译直接失败
Checker C++ 编译直接失败
Linter TypeScript 否(可跳过) 输出警告/建议

6.6 CLI 执行方式

ArkTS 的编译检查有两条命令行路径。

路径一:编译器 CLI(强制执行 Parser + AST Verifier + Checker)

编译时自动执行前三阶段检查,出错直接报错:

# ets2panda(ArkTS-Sta,推荐)
ets2panda --extension=ets --opt-level=0 --output=out.abc input.ets

# es2abc(ArkTS-Dyn)
es2abc --extension=ets --output=out.abc input.ets

额外调试选项:

ets2panda --ast-verifier:phases=each       # 每个阶段后运行 AST Verifier
ets2panda --ast-verifier:full-program      # 最终产物全量校验
ets2panda --dump-ets-src-after-phases=...  # 查看某个阶段后的源码状态

路径二:Linter CLI(可选执行,ArkTS-Static 合规性检查)

独立于编译管线的 TypeScript 工具,需要 Node.js 环境:

# 单文件扫描
node dist/tslinter.js --arkts-2 --autofix input.ets

# IDE 交互模式
node dist/tslinter.js --ide-interactive --arkts-2 --sdk-external-api-path /path/to/sdk input.ets

两条路径对比:

编译器 (ets2panda/es2abc) Linter (tslinter)
检查阶段 Parser + AST Verifier + Checker 仅 Linter
语言 C++ TypeScript
是否内置 编译管线的一部分 独立工具,需手动调用
出错行为 编译直接失败 输出警告/建议

实际开发中,DevEco Studio 在保存/编译时自动调用 ets2panda 做前三阶段检查,Linter 通过 IDE 插件或命令行手动触发。要查看每条规则的逻辑,直接读源码 ets2panda/checker/ets/*.cpp(C++ 规则)或 linter/docs/rules/recipe*.md(Linter 规则)。

6.7 依赖分析与构建编排

ets2panda 作为编译器前端,只编译你给的单个文件。import 语句被记录在生成的 .abc 字节码的模块元数据中,但不会在编译时去查找和编译被 import 的其他 .ets 文件。

# 单文件编译:不会解析 import 依赖树
ets2panda --extension=ets --opt-level=0 --output=out.abc input.ets
# → input.ets 中的 import './other' 不会触发 other.ets 的编译
# → 生成的 out.abc 中仅记录"我需要依赖 other 模块"的元数据

这就需要构建编排层ets2panda/driver/build_system/)来负责完整的编译流程。

依赖分析工具: ets2panda/driver/dependency_analyzer/

这是一个独立的 C++ 工具,它的工作方式是用 ets2panda 的 Parser 以 DEPENDENCY_ANALYZER_MODE 模式解析每个 .ets 文件(只解析到 AST,不做类型检查),然后从 ImportPathManager 中提取 import 语句的解析结果:

// dep_analyzer.cpp:147-198
int DepAnalyzer::AnalyzeDeps(const std::string &exec, const std::string &arktsconfig,
                             const std::vector<std::string> &fileList) {
    for (const auto &fileToAnalyze : fileList) {
        // 为每个文件创建 Parser 上下文
        es2panda_Context *ctx = impl->CreateContextFromFile(cfg, fileToAnalyze.c_str());
        // 设置为"仅依赖分析"模式——只解析,不做类型检查
        ctxImpl->parser->SetParserStatus(ParserStatus::DEPENDENCY_ANALYZER_MODE);
        // 解析到 PARSED 状态即停止
        impl->ProceedToState(ctx, ES2PANDA_STATE_PARSED);
        // 提取所有 import 依赖
        CollectData(ctxImpl->parser->GetImportPathManager());
    }
}

ImportPathManager 在解析阶段发挥作用:Parser 每遇到一条 importrequire 语句,就将其解析结果(原始路径 → 解析后的绝对路径)注册到 ImportPathManagerImportPathManager 内部的数据结构:

// dep_analyzer.h
class DepAnalyzer {
    // file → its direct imports
    using FileDependenciesMap = std::unordered_map<
        std::string, std::unordered_set<std::string>>;
    // file → compiled .abc output path
    using FileOutputMatching = std::unordered_map<
        std::string, std::string>;
};

构建编排层(driver/build_system/,TypeScript):

hvigor(构建系统)
    │ 传入 build config JSON
    ▼
driver/build_system/src/entry.ts
    │
    ├── 1. 解析 build config(包含 sourceRoots、compileFiles)
    │
    ├── 2. 收集所有 .ets 文件(从 sourceRoots 递归扫描)
    │
    ├── 3. 运行 C++ dependency_analyzer
    │   └── 输出文件级依赖图(JSON)
    │
    ├── 4. 构建完整依赖图
    │   ├── 依赖排序(topological sort)
    │   └── 检测循环依赖
    │
    ├── 5. 分派编译任务
    │   ├── Worker 池并行编译(compile_process_worker.ts)
    │   ├── 按依赖顺序调用 ets2panda 编译每个文件
    │   └── 增量编译:hash_cache.json 缓存未变文件
    │
    ├── 6. 插件系统
    │   ├── PARSED 阶段 (解析后)
    │   ├── CHECKED 阶段(类型检查后)
    │   └── CLEAN 阶段  (编译完成后)
    │
    └── 7. merge_abc 合并
        └── 所有 .abc 文件合并为最终输出

增量编译:

编译系统使用 hash_cache.json 缓存文件哈希值。未变化的文件从依赖图中跳过——ets2panda 不会对这些文件重新编译。当源文件数超过 CLUSTER_FILES_TRESHOLD = 460 时,会触发图聚类(batch),将相邻文件合并为批次以提高并行效率。

总结:ets2panda 单文件编译时不做 import 解析,跨文件依赖分析由 dependency_analyzer(C++)负责,编译编排由 driver/build_system(TypeScript)负责,最终由 hvigor 整合调用。

6.8 IDE 中实时检查:LSP

DevEco Studio 编辑器中实时显示语法错误(红色/黄色波浪线)不走 Linter,而是通过 LSP(Language Server Protocol) 通道。

LSP 是 DevEco 编辑器中智能体验的底层引擎,位于 ets2panda/lsp/

开发者输入代码
    │
    ├── IDE(DevEco)→ LSP Client
    │   每按一次键发送 diagnostic 请求
    │
    ▼
LSP Server(ets2panda 内置进程,C++)
    │
    ├── 1. Parser:解析当前文件为 AST
    ├── 2. Checker:运行完整类型检查
    │   └── 诊断结果 → LSP Diagnostic 协议
    │       → 返回 IDE → 显示波浪线
    ├── 3. 其他 LSP 服务:
    │   ├── completions.cpp       ← 自动补全
    │   ├── get_definition_and_bound_span.cpp ← 跳转定义
    │   ├── references.cpp        ← 查找引用
    │   ├── signature_help_items.cpp ← 参数提示
    │   ├── quick_info            ← hover 类型提示
    │   ├── formatting/           ← 代码格式化
    │   └── refactors/            ← 代码重构
    └── 响应 → IDE

LSP 的诊断信息来源:

LSP 每次请求都运行完整的 Parser + Checker(跳过 Bytecode Emission 阶段)。诊断定义在 YAML 文件中,是整个编译器的唯一真相来源:

ets2panda/util/diagnostic/
├── syntax.yaml           ← 语法错误(Parser 阶段)
├── semantic.yaml         ← 语义错误(Checker 阶段,1736 行)
├── warning.yaml          ← 警告
├── fatal.yaml            ← 致命错误
└── arktsconfig_error.yaml ← 配置错误

每条诊断的注册流程:

YAML 定义 → diagnostic.rb 代码生成器 → diagnostic.h/.cpp
          → code_fix_register.h (LSP code action 注册)
          → Checker 运行时触发 → LSP 封装为 Diagnostic 协议 → IDE

Linter 的角色:

Linter 以独立 Node.js 进程运行,不是 LSP 的一部分。在 DevEco 中:

通道 引擎 触发方式 检查内容 响应时间
LSP (Checker) C++ (ets2panda) 每次按键,自动 类型错误、语义错误 毫秒级
Linter TypeScript IDE 操作/命令行手动 ArkTS-Static 合规、代码规范 秒级
编译时 C++ (ets2panda) hb build 全部(含 Bytecode Emission) 分钟级

总结: DevEco 编辑器中实时波浪线走的是 LSP(C++ 进程)→ Checker → Parser 链路。Linter 是独立的项目级扫描工具,不在编辑器的实时路径上。

7. 执行引擎:解释器 / JIT / AOT

7.1 三层执行策略

ArkTS 运行时支持三层执行,优先级从高到低:

AOT 预编译 (.an 文件)
    → 直接调用机器码(最快)
    ↓ 不存在或版本不匹配
JIT 编译 (热点代码)
    → 运行时编译为机器码
    ↓ 冷代码(首次执行或低频执行)
解释器
    → 逐条执行字节码

7.2 解释器(Interpreter)

位置:ecmascript/interpreter/

解释器逐条执行 .abc 字节码指令。字节码指令集(ISA)定义在 runtime_core/isa/ 中,用 YAML 描述。

解释器执行过程中会启动 Inline Cache(IC)ecmascript/ic/)来优化属性访问。IC 记录对象上次访问的布局偏移量,下次访问同一类型的对象时跳过属性查找。

7.3 JIT 编译器

位置:ecmascript/jit/

JIT 基于运行时 profiling 信息(热点计数、类型反馈)将频繁执行的字节码编译为机器码。启用的应用列表在 ohos/app_aot_jit_enable_list.conf 中配置。

7.4 AOT 编译器

位置:ecmascript/compiler/

AOT 在应用编译阶段将字节码编译为 .an 机器码文件,与 HAP 一起分发。运行时直接加载 .an 文件执行,跳过解释和 JIT 编译。

AOT 产物:

.abc (字节码)
  │
  ▼ ark_aot_compiler
  │
.an (机器码) + .ai (AOT 信息)
  │
  ▼ 运行时加载
  AotFileManager::LoadAotFile()

AOT 启用列表在 ohos/enable_aot_list.conf,系统应用(SystemUI、Launcher 等)通常开启。

8. 内存管理与 GC

8.1 GC 策略

运行时支持两种 GC 算法:

GC 类型 位置 特点
CMS-GC ecmascript/mem/cms_gc.h 并发标记-清除,暂停时间短
CMC-GC (Partial-Compressing) ecmascript/mem/ 并发标记-紧凑,减少内存碎片

GC 线程通过 daemon/ 模块(ecmascript/daemon/)管理,独立于应用线程运行。

8.2 堆结构

Heap (ecmascript/mem/heap.h)
  ├── SemiSpace (新生代,复制 GC)
  │   ├── From Space
  │   └── To Space
  ├── Old Space (老年代,CMS/CMC)
  ├── Huge Object Space (大对象)
  ├── Snapshot Space (快照区域 —— 只读)
  └── Shared Heap (跨 VM 共享对象)

Snapshot Space 是 ArkTS 运行时特有的设计——快照反序列化后的对象放在只读的 Snapshot Space 中,永不 GC。

9. 模块系统与并发

9.1 ES Module 实现

基于 ECMAScript 2015 模块规范,在 ecmascript/module/ 中实现:

// 模块类型(ecmascript/module/)
enum class ModuleTypes {
    ECMA_MODULE,    // 标准 ES Module
    CJS_MODULE,     // CommonJS
    JSON_MODULE,    // JSON 模块
    NATIVE_MODULE,  // NAPI 原生模块
    OHOS_MODULE,    // OHOS 特有模块(如 @ohos.*)
    APP_MODULE,     // 应用模块
    INTERNAL_MODULE,
    STATIC_MODULE   // 静态模块
};

9.2 TaskPool 并发

位置:ecmascript/taskpool/

基于 Actor 模型的并发调度框架,提供比 Promise 更高效的并行执行:

主线程 (EcmaVM #1)
    │
    ├── TaskPool::Execute(task)
    │   ├── 检查 Worker 池
    │   ├── 有空闲 Worker → 直接提交
    │   └── 无空闲 → 按优先级排队
    │
    ▼
Worker 线程 (EcmaVM #2, 隔离堆)
    │
    ├── 执行任务(与主线程并行)
    ├── 通过 SharedObject 传回结果
    └── 返回到主线程 Event Loop

Worker 线程有自己的隔离 VM,不能直接访问主线程堆上的对象。通过 Serializer 序列化传输。

10. 快照:加速启动的关键设计

ArkTS 运行时使用三种快照,分别加速 VM 初始化、模块加载和字节码加载。§10 描述的是第一种——VM 堆快照。 后两种在 §10.5 中说明。

10.1 VM 堆快照(Builtins Snapshot)

解决的问题: 每次创建 EcmaVM 时,都重复创建 Array.prototype、Object.prototype、Function 构造函数等标准对象。快照把首次初始化后的堆状态序列化,下次直接反序列化。

10.2 快照类型

// ecmascript/snapshot/snapshot.h
enum class SnapshotType {
    VM_ROOT = 0,   // 完整 VM 状态(堆 + 内置对象)
    BUILTINS = 1,  // 标准库内置对象
    AI = 2,        // AOT 编译快照
};

10.3 序列化与反序列化

首次启动:
    EcmaVM::Create()
    → 创建标准对象(Array/Object/Function/...)
    → Builtins::Initialize()(加载全部标准库)
    → Snapshot::Serialize()(写为 .snapshot 文件)

后续启动:
    EcmaVM::Create()
    → SnapshotProcessor::Deserialize()
        ├── 读取 Snapshot 二进制
        ├── 解码 EncodeBit(64 位压缩对象编码)
        ├── 分配 Region(区域内存)
        ├── 定位对象(Region + Offset)
        └── 还原完整堆状态
    → 跳过 Builtins::Initialize()
    → 启动时间降低约 60%

10.4 EncodeBit 压缩编码

快照中的每个对象用 64 位 EncodeBitecmascript/snapshot/encode_bit.h)编码:

┌──────────────────────────────────────────────────────────────┐
│  EncodeBit (64 bits)                                         │
│  ┌──────┬────────┬────┬──────┬────┬──────┬──────┬──────┐   │
│  │Region│ Offset │Str │ Type │Spec│Global│  Ref │Unused│   │
│  │ 10b  │  20b   │ 1b │  8b  │ 1b │  1b  │ 16b  │  6b  │   │
│  └──────┴────────┴────┴──────┴────┴──────┴──────┴──────┘   │
└──────────────────────────────────────────────────────────────┘

这种编码方式不序列化整个对象图,而是从已布局好的堆空间中直接定位——反序列化几乎是 O(1) 的。

10.5 ModuleSnapshot 与 JSPandaFileSnapshot

除了 VM 堆快照(序列化 Builtins 标准库),还有两种快照用于加速工作线程(TaskPool Worker)的 EcmaVM 启动:

// ecmascript/snapshot/common/modules_snapshot_helper.h
enum class SnapshotFeatureState : int8_t {
    DEFAULT = 0,
    PANDAFILE = 1 << 0,  // JSPandaFile 快照:缓存 .abc 文件解析结果
    MODULE = 1 << 1,     // Module 快照:缓存 ES Module 链接状态
};
快照类型 序列化什么 跳过什么初始化步骤
VM 堆快照(§9.1) ECMAScript 标准库对象(Builtins) Builtins::Initialize()
ModuleSnapshot ES Module 记录 + 导出名表 + 依赖图 Module::Resolve() / Module::Instantiate()
JSPandaFileSnapshot .abc 的 MethodLiteral 索引 + Program JSPandaFile::Load()

实际流程:

主 VM 首次启动:
    ├── VM 堆快照反序列化 → 跳过 Builtins 初始化
    ├── JSPandaFile::Load() → 解析 .abc
    ├── Module::Resolve() → 解析 import 依赖树
    └── 完成后创建额外快照:
        ├── MarkJSPandaFileSnapshotLoaded()  → 缓存 .abc 解析结果
        └── MarkModuleSnapshotLoaded()        → 缓存模块链接状态

Worker VM 启动(TaskPool):
    ├── VM 堆快照反序列化
    ├── JSPandaFileSnapshot(共享,不重新打开 .abc)
    ├── ModuleSnapshot(共享,不重新解析 import)
    └── → 直接进入 Evaluate 阶段

这解释了为什么 §3.3 中 TaskPool Worker 的初始化内容称为"共享 ModuleSnapshot"——Worker 不需要自己的模块解析,直接从主 VM 的 ModuleSnapshot 反序列化即可。

源码入口: ecmascript/snapshot/common/modules_snapshot_helper.h

11. 系统交互与权限控制

11.1 三层交互路径

ArkTS 运行时与系统之间通过三层路径交互:

ArkTS 代码
    │
    ├── **NAPI 层**(直接调用系统 C++ SDK)
    │   │  例:import { hilog } from '@ohos.hilog'
    │   │    → NAPI FunctionRef → C++ callback → OHOS SDK → 内核
    │   │
    ├── **ACE 框架层**(UI 组件交互)
    │   │  例:@Component → build() { Column() { ... } }
    │   │    → FrameworkHelper 桥接 → ace_engine 渲染 → 显示驱动
    │   │
    └── **系统服务层**(通过 IPC 调用 Foundation SA)
        │  例:import { bundleManager } from '@ohos.bundle.bundleManager'
        │   → NAPI → IPC → Foundation → BMS 服务

11.2 NAPI 桥接

ArkTS 调用 C++ 原生代码的标准接口,位于 ecmascript/napi/

// jsnapi.cpp: 创建 C++ 函数暴露给 ArkTS
JSHandle<JSTaggedValue> MyNativeFunc(JsiRuntimeCallInfo *info) {
    EcmaVM *vm = info->GetVM();
    // 处理 ArkTS 传入的参数
    int32_t argc = info->GetArgsNumber();
    Local<JSValueRef> arg = info->GetCallArg(0);
    // 调用 OHOS SDK 原生函数
    int result = HiLogPrint(LOG_APP, LOG_INFO, 0, "domain", "%{public}s", str);
    // 返回给 ArkTS
    return JSHandle<JSTaggedValue>::Cast(IntegerRef::New(vm, result));
}

ArkTS 中的 import { xxx } from '@ohos.xxx' 最终映射到 NAPI 注册的 C++ 模块。整个链路由 SDK 构建系统管理:

接口声明(interface/sdk-js/)→ NAPI 注册 → C++ SDK 实现 → 系统服务

11.3 FrameworkHelper(ACE 框架桥接)

ecmascript/ohos/framework_helper.h: FrameworkHelper
├── 管理 ACE 框架与运行时之间的状态同步
├── @State 属性变更通知 → ACE 渲染管线
└── 组件树更新

典型流程:
ArkTS: this.count++        // @State 装饰属性
    │
    ├── EcmaVM 执行字节码
    ├── FrameworkHelper 检测到属性变化
    └── ACE::Layout() + Render() 更新 UI

11.4 代码解密(系统应用保护)

系统应用通过硬件密钥加密 .abc 字节码,运行时通过内核驱动解密:

// ecmascript/ohos/code_decrypt.h
#define DEV_APP_CRYPTO_PATH "/dev/code_decrypt"
#define CODE_DECRYPT_CMD_SET_KEY  _IOW('c', 0x01, struct code_decrypto_arg)

struct code_decrypto_arg {
    int arg1_len;
    int arg2_len;
    void *arg1;   // 应用标识
    void *arg2;   // 密钥材料
};

流程:加密 .abc → 安装时注册密钥到内核驱动 → 运行时透明解密 → 执行

11.5 代码权限控制

ArkTS 的权限控制是编译时 + 安装时 + 运行时三层机制。运行时本身不做权限校验——它只负责执行 .abc 字节码。

层级        机制                         作用
──────────────────────────────────────────────────────
编译器      禁止 eval / new Function     防止运行时动态代码生成
编译时      强制类型标注(Checker)        AOT 编译可行性保证
编译时      Linter (--arkts-2)            ArkTS-Static 合规
安装时      包签名 + HAP 校验             确保 HAP 来源可信
运行时      NAPI → IPC → SA 校验 ACL     系统服务层权限检查
文件系统    内核驱动代码解密                保护字节码不被反编译

为什么运行时不做权限校验:

ArkTS 没有类似 Android 的 Permission.checkCallingPermission()——因为所有系统 API 调用最终都通过 IPC 到达对应的 SA(System Ability)。权限校验在 SA 端执行,依据调用方的 APL 等级决定:

// 系统服务(如 BMS)接收 IPC 请求时校验:
// - APL normal → 只能操作自己的数据
// - APL system_basic → 可操作部分系统数据
// - APL system_core → 可操作全部数据

11.6 AOT 包签名验证

在 AOT 编译阶段,运行时调用系统库验证应用包签名:

// ecmascript/ohos/ohos_pkg_verifier.h
class OhosPkgVerifier {
    // 在 AOT 编译前验证 bundleName + appIdentifier
    // 校验失败:AOT 编译被拒绝
    static bool VerifyPkgInfo(AotCompilerPreprocessor &cPreprocessor,
                              CompilationOptions &cOptions) {
        if (!cPreprocessor.GetMainPkgArgs()) return true;
        // dlopen libhapverify.z.so
        // 调用 ParseBundleNameAndAppIdentifier()
        // 验证 HAP 签名与 bundleName 一致
    }
};

这意味着只有经过签名验证的 HAP 才能触发 AOT 编译,防止恶意代码通过 AOT 路径植入。

12. 总结

12.1 核心链路

.ts/.ets → es2abc → .abc → HAP → install → 
AppSpawn → EcmaVM::Create() → Snapshot反序列化 → 
JSPandaFile加载 → Module解析 → Interpreter/JIT/AOT 执行

12.2 关键设计点

  1. 按需初始化:每个应用进程一个 EcmaVM,不在系统启动时集中预热。标准库通过快照反序列化加速,而非每次重新创建。

  2. 三层执行引擎:AOT(最快)→ JIT(热点编译)→ Interpreter(兜底)。AOT 用于预置系统应用,JIT 用于运行中优化,解释器保证冷代码也能执行。

  3. 快照加速:通过 EncodeBit 压缩编码(64位/对象)实现 O(1) 反序列化,启动时间降低约 60%。

  4. Actor 并发:TaskPool 基于隔离 Worker 模型,每个 Worker 有独立 VM,避免共享堆的 GC 暂停问题。

  5. 严格安全:仅支持严格模式,禁止 evalnew Function() 等动态代码执行——这在编译时由 es2abc 保证,运行时完全不存在"动态生成字节码"的路径。

12.3 FAQ

Q: ArkTS 运行时和 V8 或 JavaScriptCore 有什么区别?
A: 最大区别是 ArkTS 运行时不支持动态代码生成(evalnew Function)。所有代码在编译时(es2abc)就已经确定。这消除了 JIT 的"代码缓存失效"优化难题,也使 AOT 编译可行。

Q: 快照文件存在哪里?
A: 存在于 system 分区的预置文件中,运行时首次加载后也可能会缓存到 data 分区。快照配置在 system/etc/ark/app_startup_snapshot.json 中。

Q: 一个应用启动时,运行时初始化需要多久?
A: 快照反序列化是 O(1) 的(EncodeBit 编码 + Region 定位),通常在毫秒级。标准库初始化被跳过。瓶颈通常在 .abc 文件加载和 Module 解析阶段。

Q: AOT 编译的 .an 文件和 .abc 文件什么关系?
A: .an 是 .abc 的机器码编译产物。应用发布时同时包含 .abc 和 .an。运行时优先加载 .an,不存在或版本不匹配时 fallback 到解释器 + JIT。

Q: 编译时 import 依赖如何解析?
A: ets2panda 单文件编译时不解析 import 依赖树,import 仅作为模块元数据记录在 .abc 中。真正的跨文件依赖分析由 dependency_analyzer(C++ 工具)在构建编排阶段完成,输出拓扑排序的依赖图,然后按序编译。

12.4 核心概念与文件速查

概念 一句话 源码路径
EcmaVM 每个 UIAbility 一个 VM 实例,管理线程/堆/GC ets_runtime/ecmascript/ecma_vm/
JSPandaFile .abc 文件的运行时封装,含 Program + ConstantPool ets_runtime/ecmascript/jspandafile/js_pandafile.h
Program .abc 的入口对象,引用入口函数和常量池 runtime_core/libpandafile/program.h
ConstantPool 所有字面量的集合(字符串/方法/类) ets_runtime/ecmascript/jspandafile/constpool_value.h
Interpreter 逐条执行字节码指令 ets_runtime/ecmascript/interpreter/
JIT 热点代码编译为机器码 ets_runtime/ecmascript/jit/
AOT 编译时生成 .an 机器码,运行时直接加载 ets_runtime/ecmascript/compiler/
Inline Cache 缓存对象属性访问路径,跳过查找 ets_runtime/ecmascript/ic/
Snapshot 运行时堆快照,序列化/反序列化加速启动 ets_runtime/ecmascript/snapshot/snapshot.h
EncodeBit 64 位压缩对象编码(Region + Offset + Type) ets_runtime/ecmascript/snapshot/encode_bit.h
CMS-GC 并发标记清除 GC ets_runtime/ecmascript/mem/
CMC-GC 并发标记紧凑 GC(减少碎片) ets_runtime/ecmascript/mem/
SourceTextModule ES Module 规范实现的模块记录 ets_runtime/ecmascript/module/
ModuleManager 模块加载/生命周期管理 ets_runtime/ecmascript/module/js_module_manager.h
TaskPool Actor 模型并发调度,自动扩缩 Worker ets_runtime/ecmascript/taskpool/
NAPI C++ 原生 API 桥接 ets_runtime/ecmascript/napi/
Builtins ECMAScript 标准库(Array/Function/Regex 等) ets_runtime/ecmascript/builtins/
SnapshotProcessor 快照序列化/反序列化引擎 ets_runtime/ecmascript/snapshot/snapshot_processor.h
ModuleSnapshot ES Module 链接状态的快照,Worker 共享避免重复解析 ets_runtime/ecmascript/snapshot/common/modules_snapshot_helper.h
JSPandaFileSnapshot .abc 解析结果的快照,Worker 共享避免重复加载 ets_runtime/ecmascript/snapshot/common/modules_snapshot_helper.h
DepAnalyzer 编译时依赖分析工具,解析 import 拓扑 ets_frontend/ets2panda/driver/dependency_analyzer/dep_analyzer.h
BuildSystem 构建编排层,管理多文件编译顺序和 Worker 池 ets_frontend/ets2panda/driver/build_system/
posted @ 2026-06-04 15:00  getmoon  阅读(53)  评论(0)    收藏  举报