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,共享数据通过 SharedModuleManager 和 SharedObject 机制跨 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_packages 由 BUILD.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 |
| 禁止动态代码 | 禁止 eval、new 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 每遇到一条 import 或 require 语句,就将其解析结果(原始路径 → 解析后的绝对路径)注册到 ImportPathManager。ImportPathManager 内部的数据结构:
// 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 位 EncodeBit(ecmascript/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 关键设计点
-
按需初始化:每个应用进程一个 EcmaVM,不在系统启动时集中预热。标准库通过快照反序列化加速,而非每次重新创建。
-
三层执行引擎:AOT(最快)→ JIT(热点编译)→ Interpreter(兜底)。AOT 用于预置系统应用,JIT 用于运行中优化,解释器保证冷代码也能执行。
-
快照加速:通过
EncodeBit压缩编码(64位/对象)实现 O(1) 反序列化,启动时间降低约 60%。 -
Actor 并发:TaskPool 基于隔离 Worker 模型,每个 Worker 有独立 VM,避免共享堆的 GC 暂停问题。
-
严格安全:仅支持严格模式,禁止
eval、new Function()等动态代码执行——这在编译时由 es2abc 保证,运行时完全不存在"动态生成字节码"的路径。
12.3 FAQ
Q: ArkTS 运行时和 V8 或 JavaScriptCore 有什么区别?
A: 最大区别是 ArkTS 运行时不支持动态代码生成(eval、new 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/ |

浙公网安备 33010602011771号