你的位置:色播五月 > porn 丝袜 > 成人电影院 史无先例, 移植V8造谣机到纯血鸿蒙系统
成人电影院 史无先例, 移植V8造谣机到纯血鸿蒙系统
发布日期:2024-09-19 08:56    点击次数:120

成人电影院 史无先例, 移植V8造谣机到纯血鸿蒙系统

一、布景成人电影院

如图所示,Roma框架是咱们自主研发的动态化跨平台处分决议,已营救iOS,android,web三端。现在在京东金融APP照旧有200+页面,200+乐高楼层使用,为保证基于Roma框架开发的业务不错零本钱、无缝运行到鸿蒙系统,需要将Roma框架适配到鸿蒙系统。

Roma框架是基于JS引擎运行的,在iOS系统使用系统内置的JavascriptCore,在Android系统使用V8,关联词,鸿蒙系统却莫得不错实施Roma框架的JS引擎,因此需要移植一个JS引擎到鸿蒙平台。

二、JS引擎选型

现在主流的JS引擎有以下这些:

引擎称呼

应用代表

公司

V8

Chrome/Opera/Edge/Node.js/Electron

Google

SpiderMonkey

firefox

Mozilla

JavaScriptCore

Safari

Apple

Chakra

IE

Microsoft

Hermes

React Native

Facebook

JerryScript/duktape/QuickJS

袖珍况且可镶嵌的Javascript引擎/主要应用于IOT斥地

-

其中最流行的是Google开源的V8引擎,除了Chrome等浏览器,Node.js亦然用的V8引擎。Chrome的市集占有率高达60%,而Node.js是JS后端编程的事实法度。另外,Electron(桌面应用框架)是基于Node.js与Chromium开发桌面应用,亦然基于V8的。国内的繁密浏览器,其实也王人是基于Chromium浏览器开发,而Chromium相当于开源版块的Chrome,天然亦然基于V8引擎的。致使连浏览器界独树一帜的Microsoft也投奔了Chromium阵营。V8引擎使得JS不错应用在Web、APP、桌面端、做事端以及IOT等各个范围。

三、V8引擎的使命旨趣

V8的主要任务是实施JavaScript代码,况且大概处理JavaScript源代码、即时编译(JIT)代码以及实施代码。v8是一个相当复杂的技俩,有朝上100万行C++代码。

下图展示了它的基本使命经过:

如图所示,它通过词法分析、语法分析、字节码生成与实施、即时编译与机器码生成以及垃圾回收等材干,兑现了对JavaScript源代码的高效实施。此外,V8引擎还通过监控代码的实施情况,对热门函数进行自动优化,从而进一步提高了代码的实施性能。其中 Parser(解析器)、Ignition(证明器)、TurboFan(编译器) 、Orinoco(垃圾回收)是 V8 中四个中枢使命模块,对应的V8源码目次如下图。

1、Parser:解析器

负责将JavaScript源码疗养为Abstract Syntax Tree (AST)抽象语法树,解析过程分为:词法分析(Lexical Analysis)和语法分析(Syntax Analysis)两个阶段。

1.1、词法分析

V8 引擎首先会扫描系数的源代码,进行词法分析(Tokenizing/Lexing)(词法分析是通过 Scanner 模块来完成的)。也称为分词,是将字符串面孔的代码疗养为象征(token)序列的过程。这里的token是一个字符串,是组成源代码的最小单元,访佛于英语中单词,举例,var a = 2; 经过词法分析得到的tokens如下:

从上图中不错看到,这句代码最终被领悟出了五个词法单元:

var 要道字

a 象征符

= 运算符

2 数值

;分号

一个不错在线稽察Tokens的网站: https://esprima.org/demo/parse.html

丁香五月花

1.2、语法分析

语法分析是将词法分析产生的token按照某种给定的面孔文法(这里是JavaScript话语的语法规矩)疗养成抽象语法树(AST)的过程。也即是把单词组合成句子的过程。这个过程会分析语法失误:遭遇失误的语法会抛出颠倒。AST是源代码的语法结构的树形暗示。AST包含了源代码中的系数语法结构信息,但不包含代码的实施逻辑。

举例, var a = 2; 经过语法分析青年景的AST如下:

不错看到这段法度的类型是 VariableDeclaration,也即是说这段代码是用来声明变量的。

一个不错在线稽察AST结构的网站:https://astexplorer.net/

2、Ignition:(interpreter)证明器

负责将AST疗养成字节码(Bytecode)并逐行证明实施字节码,提供快速的启动和较低的内存使用,同期会象征热门代码,集合TurboFan优化编译所需的信息,比如函数参数的类型。

2.1、什么是字节码?

字节码(Bytecode)是一种介于AST和机器码之间的中间暗示面孔,它比AST更接近机器码,它比机器码更抽象,也更轻量,与特定机器代码无关,需要证明器转译后材干成为机器码。字节码平凡不像源码一样不错让东说念主阅读,而是编码后的数值常量、援用、教导等组成的序列。

2.2、字节码的优点

•不针对特定CPU架构

•比原始的高档话语疗养成机器话语更快

•字节码比机器码占用内存更小

•期骗字节码,不错兑现Compile Once,Run anywhere(一次编译到处运行)。

早期版块的 V8 ,并莫得生成中间字节码的过程,而是将系数源码疗养为了机器代码。机器代码天然实施速率更快,关联词占用内存大。

2.3、稽察字节码

Node.js是基于V8引擎兑现的,因此node敕令提供了好多V8引擎的选项,咱们不错通过这些选项,稽察V8引擎中各个阶段的居品。使用node的--print-bytecode选项,不错打印出Ignition生成的Bytecode。

示例test.js如下

//test.jsfunction add(a, b){ return a + b;} add(1,2); //V8不会编译莫得被调用的函数,因此需要在临了一转调用add函数

运行底下的node敕令,打印出Ignition生成的字节码。

node --print-bytecode test.js[generated bytecode for function: add (0x29e627015191 )]Bytecode length: 6Parameter count 3Register count 0Frame size 0OSR urgency: 0Bytecode age: 0 33 S> 0x29e627015bb8 @ 0 : 0b 04 Ldar a1 41 E> 0x29e627015bba @ 2 : 39 03 00 Add a0, [0] 44 S> 0x29e627015bbd @ 5 : a9 ReturnConstant pool (size = 0)Handler Table (size = 0)Source Position Table (size = 8)0x29e627015bc1

适度台输出的内容相当多,临了一部分是add函数的Bytecode。

字节码的详备信息如下:

•[generated bytecode for function: add (0x29e627015191 )]: 这行告诉咱们,接下来的字节码是为 add 函数生成的。0x29e627015191 是这个函数在内存中的地址。

•Bytecode length: 6: 系数这个词字节码的长度是 6 字节。

•Parameter count 3: 该函数有 3 个参数。包括传入的 a,b 以及 this。

•Register count 0: 该函数莫得使用任何寄存器。

•Frame size 0: 该函数的帧大小是 0。帧大小是指在调用栈上分派给这个函数的空间大小,用于存储局部变量、函数参数等。

•OSR urgency: 0: On-Stack Replacement(OSR)优化的迫切进程是 0。OSR 是一种在运行时将证明实施的函数替换为编译实施的函数的本领,用于提高性能。

•Bytecode age: 0: 字节码的年事是 0。字节码的年事是指它被实施的次数,年事越高,证明这个字节码被实施的越平凡,可能会被 V8 引擎优化。

•Ldar a1 暗示将寄存器中的值加载到累加器中 ,这行是字节码的第一条教导

•Add a0, [0] 从 a0 寄存器加载值况且将其与累加器中的值相加,然后将恶果再次放入累加器 。

•Return 扫尾现时函数的实施,并把适度权传给调用方,将累加器中的值行为复返值

•S> 暗示这是一个“Safepoint”教导,V8 引擎不错在实施这条教导时进行垃圾回收等操作。

•E> 暗示这是一个“Effect”教导,可能会改变法度的景况。

•Constant pool (size = 0): 常量池的大小是 0。常量池是用来存储函数中使用的常量值的。

•Handler Table (size = 0): 颠倒处理表的大小是 0。颠倒处理表是用来处理函数中可能出现的颠倒的。

•Source Position Table (size = 8): 源代码位置表的大小是 8。源代码位置表是用来将字节码教导与源代码行号关联起来的,便捷调试。

•0x29e627015bc1 : 这行是源代码位置表的具体内容,领略了每个字节码教导对应的源代码行号和列号。

不错看到,Bytecode某种进程上即是汇编话语,只是它莫得对应特定的CPU,或者说它对应的是造谣的CPU。这样的话,生成Bytecode时简便好多,无需为不同的CPU分娩不同的代码。要知说念,V8营救9种不同的CPU,引入一个中间层Bytecode,不错简化V8的编译经过,提高可推广性。若是咱们在不同硬件上去生成Bytecode,生成代码的教导是一样的.

3、TurboFan:(compiler)编译器

V8 的优化编译器亦然v8兑现即时编译(JIT)的中枢,负责将热门函数的字节码编译成高效的机器码。

3.1、什么是JIT?

咱们需要先了解一下JIT(Just in Time)即时编译。

在运行C、C++以及Java等法度之前,需要进行编译,不可径直实施源码;但对于JavaScript来说,咱们不错径直实施源码(比如:node server.js),它是在运行的时候先编译再实施,这种景观被称为即时编译(Just-in-time compilation),简称为JIT。因此,V8也属于JIT编译器。

兑现JIT编译器的系统平凡会继续地分析正在实施的代码,并信托代码的某些部分,在这些部分中,编译或重新编译所赢得的加速将朝上编译该代码的支拨。 JIT编译是两种传统的机器代码翻译行为——提前编译(AOT)和证明——的结合,它结合了两者的优点和弱点。梗概来说,JIT编译将编译代码的速率与证明的纯真性、证明器的支拨以及非凡的编译支拨(而不单是是证明)结合起来。

除了V8引擎,Java造谣机、PHP 8也用到了JIT。

3.2、V8引擎的JIT

V8的JIT编译包括多个阶段,从生成字节码到生成高度优化的机器码,证据JavaScript代码的实施脾性动态地优化代码,以兑现高性能的JavaScript实施。看下图Ignition 和 TurboFan 的交互:

当 Ignition 起初实施 JavaScript 代码后,V8 会一直不雅察 JavaScript 代码的实施情况,并记载实施信息,如每个函数的实施次数、每次调用函数时,传递的参数类型等。若是一个函数被调用的次数朝上了内设的阈值,监视器就会将现时函数象征为热门函数(Hot Function),并将该函数的字节码以及实施的联系信息发送给 TurboFan。TurboFan 会证据实施信息作念出一些进一步优化此代码的假设,在假设的基础上将字节码编译为优化的机器代码。若是假设建设,那么当下一次调用该函数时,就会实施优化编译后的机器代码,以提高代码的实施性能。

若是假设不建设,上图中,绿色的线,是“去优化(Deoptimize)”的过程,若是TurboFan生成的优化机器码,对需要实施的代码不适用,会把优化的机器码,重新疗养成字节码来实施。这是因为Ignition集合的信息可能是失误的。

举例:

function add(a, b) {return a + b;}add(1, 2);add(2, 2);add("1", "2");

add函数的参数之前是整数,自后又形成了字符串。生成的优化机器码照旧假设add函数的参数是整数,那天然是失误的,于是需要进行去优化,Deoptimize为Bytecode来实施。

TurboFan除了上头基于类型作念优化和反优化成人电影院,还有包括内联(inlining)和潜逃分析(Escape Analysis)等,内联即是将联系联的函数进行归并。举例:

function add(a, b) { return a + b}function foo { return add(2, 4)}

内联优化后:

function fooAddInlined { var a = 2 var b = 4 var addReturnValue = a + b return addReturnValue}// 因为 fooAddInlined 中 a 和 b 的值王人是信托的,是以不错进一步优化function fooAddInlined { return 6}

使用node敕令的--print-code以及--print-opt-code选项,不错打印出TurboFan生成的汇编代码。

node --print-code --print-opt-code test.js

4、Orinoco:垃圾回收

一个高效的垃圾回收器,用于自动不断内存,回收不再使用的对象内存;它使用多种垃圾回收计谋,如分代回收、象征-铲除、增量象征等,以兑现高效内存不断。

Orinoco的主要特色包括:

•并发象征: Orinoco使用并发象征本领来减少垃圾回收的停顿时期(Pause Time)。这意味着在应用法度接续实施的同期,垃圾回收器不错在后台进行象征操作。

•增量式垃圾回收: Orinoco营救增量式垃圾回收,这允许垃圾回收器在小的时期片内实施部分垃圾回收使命,而不是一次性处理系数的垃圾。

•更高效的内存不断: Orinoco引入了一些新的内存不断计谋和数据结构,旨在减少内存碎屑和提高内存期骗率。

•可推广性: Orinoco的蓄意商量了可推广性,使得它不错顺应不同的使命负载和硬件树立。

•多线程营救: Orinoco营救多线程环境,不错期骗多核CPU来加速垃圾回收过程。

四、V8移植器具选型

咱们的开发环境各类各样可能系统是Mac,Linux或者Windows,架构是x86或者arm,是以要想编译出不错跑在鸿蒙系统上的v8库咱们需要使用交叉编译,它是在一个平台上为另一个平台编译代码的过程,允许咱们在一个平台上为另一个平台生成可实施文献。这在镶嵌式系统开发中尤为常见,因为许多镶嵌式斥地的硬件资源有限,不合乎径直在上头编译代码。 交叉编译需要一个特定的编译器、集聚器和库,这些王人是为目的平台蓄意的。此外,开发者还需要确保代码莫得平台联系的依赖,不然编译可能会失败。

v8官网上对于交叉编译Android和iOS平台的V8照旧有详备的先容。尚无对于鸿蒙OHOS平台的文档。V8官方使用的构建系统是gn + ninja。gn 是一个元构建系统,首先由 Google 开发,用于生成 Ninja 文献。它提供了一个声明式的景观来界说技俩的依赖关系、编译选项和其他构建参数。通过运行 gn gen 敕令,不错生成一个 Ninja 文献。访佛于camke + make构建系统。

gn + ninja的构建经过如下:

通过稽察鸿蒙sdk,咱们发现鸿蒙提供给开发者的native构建系统是cmake + ninja,是以咱们决定将v8官方接管的gn + ninja转成cmake + ninja。这就需要将gn语法的构建树立文献转成cmake的构建树立文献。

1、CMake简介

CMake是一个开源的、跨平台的构建系统。它不仅不错生成法度的Unix Makefile结合make敕令使用,还大概生成build.ninja文献结合ninja使用,还不错为多种IDE生成技俩文献,如Visual Studio、Eclipse、Xcode等。这种跨平台性使得CMake在多种操作系统和开发环境中王人大概无缝使命。

cmake的构建经过如下:

CMake构建主要过程是编写CMakeLists.txt文献,然后用cmake敕令将CMakeLists.txt文献编削为make所需要的Makefile文献或者ninja需要的build.ninja文献,临了用make敕令或者ninja敕令实施编译任务生成可实施法度或分享库(so(shared object))。

完好CMakeLists.txt文献的主要树立样例:

# 1. 声明条目的cmake最低版块cmake_minimum_required( VERSION 2.8 )# 2. 添加c++11法度营救#set( CMAKE_CXX_FLAGS "-std=c++11" )# 3. 声明一个cmake工程PROJECT(camke_demo)MESSAGE(STATUS "Project: SERVER") #打印联系音书 # 4. 头文献include_directories(${PROJECT_SOURCE_DIR}/../include/mq ${PROJECT_SOURCE_DIR}/../include/incl ${PROJECT_SOURCE_DIR}/../include/rapidjson)# 5. 通过设定SRC变量,玉足吧将源代码旅途王人给SRC,若是有多个,不错径直在后头接续添加set(SRC ${PROJECT_SOURCE_DIR}/../include/incl/tfc_base_config_file.cpp ${PROJECT_SOURCE_DIR}/../include/mq/tfc_ipc_sv.cpp ${PROJECT_SOURCE_DIR}/../include/mq/tfc_net_ipc_mq.cpp${PROJECT_SOURCE_DIR}/../include/mq/tfc_net_open_mq.cpp )# 6. 创建分享库/静态库# 竖立旅途(底下生成分享库的旅途)set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)# 即生成的分享库在工程文献夹下的lib文献夹中 set(LIB_NAME camke_demo_lib)# 创建分享库(把工程内的cpp文献王人创建成分享库文献,便捷通偏激文献来调用)# 这时候只需要cpp,不需要有主函数 # ${PROJECT_NAME}是生成的库名 暗示生成的分享库文献就叫作念 lib工程名.so# 也不错有意写cmakelists来编译一个莫得主函数的法度来生成分享库,供其它法度使用# SHARED为生成动态库,STATIC为生成静态库add_library(${LIB_NAME} STATIC ${SRC}) # 7. 集聚库文献# 把刚刚生成的${LIB_NAME}库和所需的其它库集聚起来# 若是需要集聚其他的动态库,-l后接去除lib前缀和.so后缀的称呼,以集聚# libpthread.so 为例,-lpthreadtarget_link_libraries(${LIB_NAME} pthread dl) # 8. 编译主函数,生成可实施文献# 先竖立旅途set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) # 可实施文献生成add_executable(${PROJECT_NAME} ${SRC}) # 集聚这个可实施文献所需的库target_link_libraries(${PROJECT_NAME} pthread dl ${LIB_NAME})

一般把CMakeLists.txt文献放在工程目次下,使用时先创建一个叫build的文献夹(这个并非必须,因为cmake敕令指向CMakeLists.txt所在的目次,举例cmake .. 暗示CMakeLists.txt在现时目次的上一级目次。cmake实施后会生成好多编译的中间文献,是以一般提议新建一个新的目次,有意用来编译),平凡构建材干如下:

1.mkdir build2.cd build 3.cmake .. 或者 cmake -G Ninja ..4.make 或者 ninja

其中cmake .. 在build文献夹下生成Makefile。make敕令在Makefile所在的目次下实施,证据Makefile进行编译。

或者cmake -G Ninja .. 在build文献夹下生成build.ninja。ninja敕令在build.ninja所在的目次下实施,证据build.ninja进行编译。

2、CMake中的交叉编译竖立

树立景观一:

径直在CMakeLists.txt文献中,使用CMAKE_C_COMPILER和CMAKE_CXX_COMPILER这两个变量来指定C和C++的编译器旅途。使用CMAKE_LINKER变量来指定技俩的集聚器。这样,当CMake生成构建文献时,就会使用指定的编译器来编译源代码。使用指定的集聚器进行技俩的集聚操作。

以下是一个简便的竖立交叉编译器和集聚器的CMakeLists.txt文献示例:

# 指定CMake的最低版块条目cmake_minimum_required(VERSION 3.10)# 技俩称呼project(CrossCompileExample)# 竖立C编译器和C++编译器set(CMAKE_C_COMPILER "/path/to/c/compiler")set(CMAKE_CXX_COMPILER "/path/to/cxx/compiler")# 竖立集聚器set(CMAKE_LINKER "/path/to/linker")# 添加可实施文献add_executable(myapp main.cpp)

另外咱们还不错使用单独器具链文献树立交叉编译环境。

树立景观二:CMake中使用器具链文献树立

器具链文献(toolchain file)是将树立信息索取到一个单独的文献中,以便于在多个技俩中复用。包含一系列CMake变量界说,这些变量指定了编译器、集聚器和其他器具的位置,以及其他与目的平台联系的竖立,以确保它大概正确地为目的平台生成代码。它让咱们不错专注于处分本色的问题,而不是每次王人要手动树立编译器和器具。

一个基本的器具链文献示举例下:

创建一个名为toolchain.cmake的文献,并在其中界说器具链的旅途和竖立:

该技俩需要为ARM架构的Linux系统进行交叉编译

# 竖立C和C++编译器set(CMAKE_C_COMPILER "/path/to/c/compiler")set(CMAKE_CXX_COMPILER "/path/to/cxx/compiler")# 竖立集聚器set(CMAKE_LINKER "/path/to/linker")# 指定目的系统的类型 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) # 其他与目的平台联系的竖立 # ...

在实施cmake敕令构建时,使用-DCMAKE_TOOLCHAIN_FILE参数指定器具链文献的旅途:

cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake /path/to/source

这样,CMake就会使用器具链文献中指定的编译器和竖立来为目的平台生成代码。

五、V8和惯例C++库移植的首要互异

惯例C++技俩按照上述交叉编译先容的树立即可完成交叉编译过程,关联词V8的移植必须充分领悟builtin和snapshot材干完成!一般的库,所谓交叉编译即是调用目的平台指定的器具链径直编译源码生成目的平台的文献。比如一个C文献要给android用,调用ndk包的gcc、clang编译即可。但由于v8的builtin本色用的是v8我方的器具链体系编译成目的平台的代码,是以并不可套用上头的景观。

1、builtin

1.1、builtin是什么

在V8引擎中,builtin即内置函数或模块。V8的内置函数和模块是JavaScript话语的一部分,提供了一些基本的功能,举例数学运算、字符串操作、日历处理等。另外ignition解析器每一条字节码教导兑现亦然一个builtin。

V8的内置函数和模块是通过C++代码兑现的,并在编译时径直集成到V8引擎中。这些内置函数和模块不需要在JavaScript代码中显式地导入或援用,就不错径直使用。

以下是一些V8的内置函数和模块的例子:

•Math对象:提供了各类数学运算的函数,举例Math.sin、Math.cos等。

•String对象:提供了字符串操作的函数,举例String.prototype.split、String.prototype.replace等。

•Date对象:提供了日历和时期处理的函数,举例Date.now、Date.parse等。

•JSON对象:提供了JSON数据的解析和生成的函数,举例JSON.parse、JSON.stringify等。

•ArrayBuffer对象:提供了对二进制数据的操作的函数,举例ArrayBuffer.prototype.slice、ArrayBuffer.prototype.byteLength等。

•WebAssembly模块:提供了对WebAssembly模块的加载和实例化的函数,举例WebAssembly.compile、WebAssembly.instantiate等。

这些内置函数和模块王人是V8引擎的关键组成部分,提供了基础的JavaScript功能。它们是V8运行时最关键的“积木块”;

1.2、builtin是怎样生成的

v8源码中builtin的编译相比绕,因为v8中大多量builtin的“源码”,其实是builtin的生成逻辑,这亦然领悟V8源码的要道。

builtin和snapshot王人是通过mksnapshot器具运行生成的。mksnapshot是v8编译过程中的一个中间居品,也即是说v8编译过程中会生成一个mksnapshot可实施法度况且会实施它生成v8后续编译需要的builtin和snapshot,就像套娃一样。

举例v8源码中字节码Ldar教导的兑现如下:

IGNITION_HANDLER(Ldar, InterpreterAssembler) { TNode value = LoadRegisterAtOperandIndex(0); SetAccumulator(value); Dispatch;}

上述代码只在V8的编译阶段由mksnapshot法度实施,实施后会产出机器码(JIT),然后mksnapshot法度把生成的机器码dump下来放到汇编文献embedded.S里,编译进V8运行时(相当于用JIT编译器去AOT)。

builtin被dump到embedded.S的对应v8源码在v8/src/snapshot/embedded-file-writer.h

void WriteFilePrologue(PlatformEmbeddedFileWriterBase* w) const { w->Comment("Autogenerated file. Do not edit."); w->Newline; w->FilePrologue; }

上述Ldar教导dump到embedded.S后汇编代码如下:

Builtins_LdarHandler:.def Builtins_LdarHandler; .scl 2; .type 32; .endef; .octa 0x72ba0b74d93b48fffffff91d8d48,0xec83481c6ae5894855ccffa9104ae800 .octa 0x2454894cf0e4834828ec8348e2894920,0x458948e04d894ce87d894cf065894c20 .octa 0x4d0000494f808b4500001410858b4dd8,0x1640858b49e1894c00000024bac603 .octa 0x4d00000000158d4ccc01740fc4f64000,0x2045c749d0ff206d8949285589 .octa 0xe4834828ec8348e289492024648b4800,0x808b4500001410858b4d202454894cf0 .octa 0x858b49d84d8b48d233c6034d00004953,0x158d4ccc01740fc4f64000001640 .octa 0x2045c749d0ff206d89492855894d0000,0x5d8b48f0658b4c2024648b4800000000 .octa 0x4cf7348b48007d8b48011c74be0f49e0,0x100000000ba49211cb60f43024b8d .octa 0xa90f4fe800000002ba0b77d33b4c0000,0x8b48006d8b48df0c8b49e87d8b4cccff .octa 0xcccccccccccccccc90e1ff30c48348c6 .byte 0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc

builtin在v8源代码v8\src\builtins\builtins-definitions.h中界说,这个文献还include一个证据ignition教导生成的builtin列表以及torque编译器生成的builtin界说,一共1700+个builtin。每个builtin,王人会在embedded.S中生成一段代码。

builtin生成的v8源代码在:v8\src\builtins\setup-builtins-internal.cc

void SetupIsolateDelegate::SetupBuiltinsInternal(Isolate* isolate) { Builtins* builtins = isolate->builtins; DCHECK(!builtins->initialized_); PopulateWithPlaceholders(isolate); // Create a scope for the handles in the builtins. HandleScope scope(isolate); int index = 0; Code code;#define BUILD_CPP(Name) \ code = BuildAdaptor(isolate, Builtin::k##Name, \ FUNCTION_ADDR(Builtin_##Name), #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_TFJ(Name, Argc, ...) \ code = BuildWithCodeStubAssemblerJS( \ isolate, Builtin::k##Name, &Builtins::Generate_##Name, Argc, #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_TFC(Name, InterfaceDescriptor) \ /* Return size is from the provided CallInterfaceDescriptor. */ \ code = BuildWithCodeStubAssemblerCS( \ isolate, Builtin::k##Name, &Builtins::Generate_##Name, \ CallDescriptors::InterfaceDescriptor, #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_TFS(Name, ...) \ /* Return size for generic TF builtins (stub linkage) is always 1. */ \ code = BuildWithCodeStubAssemblerCS(isolate, Builtin::k##Name, \ &Builtins::Generate_##Name, \ CallDescriptors::Name, #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_TFH(Name, InterfaceDescriptor) \ /* Return size for IC builtins/handlers is always 1. */ \ code = BuildWithCodeStubAssemblerCS( \ isolate, Builtin::k##Name, &Builtins::Generate_##Name, \ CallDescriptors::InterfaceDescriptor, #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_BCH(Name, OperandScale, Bytecode) \ code = GenerateBytecodeHandler(isolate, Builtin::k##Name, OperandScale, \ Bytecode); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++;#define BUILD_ASM(Name, InterfaceDescriptor) \ code = BuildWithMacroAssembler(isolate, Builtin::k##Name, \ Builtins::Generate_##Name, #Name); \ AddBuiltin(builtins, Builtin::k##Name, code); \ index++; BUILTIN_LIST(BUILD_CPP, BUILD_TFJ, BUILD_TFC, BUILD_TFS, BUILD_TFH, BUILD_BCH, BUILD_ASM);#undef BUILD_CPP#undef BUILD_TFJ#undef BUILD_TFC#undef BUILD_TFS#undef BUILD_TFH#undef BUILD_BCH#undef BUILD_ASM // ...}

BUILTIN_LIST宏内界说了系数的builtin,并证据其类型去调用不同的参数,在这里参数是BUILD_CPP, BUILD_TFJ...这些,界说了不同的生成计谋,这些参数去掉前缀代表不同的builtin类型(CPP, TFJ, TFC, TFS, TFH, BCH, ASM)

mksnapshot实施时生成builtin的景观有两种:

•径直生成机器码,ASM和CPP类型builtin使用这种景观(CPP类型只是生成适配器)

•先生成turbofan的graph(IR),然后由turbofan编译器编译成机器码,除ASM和CPP以外其它builtin类型王人是这种

举例:DoubleToI是一个ASM类型builtin,功能是把double转成整数,该builtin的JIT生成逻辑位于Builtins::Generate_DoubleToI,若是是x64的window,该函数放在v8/src/builtins/x64/builtins-x64.cc文献。由于每个CPU架构的教导王人不一样,是以每个CPU架构王人有一个兑现,放在各自的builtins-ArchName.cc文献。

x64的兑现如下:

void Builtins::Generate_DoubleToI(MacroAssembler* masm) { Label check_negative, process_64_bits, done; // Account for return address and saved regs. const int kArgumentOffset = 4 * kSystemPointerSize; MemOperand mantissa_operand(MemOperand(rsp, kArgumentOffset)); MemOperand exponent_operand( MemOperand(rsp, kArgumentOffset + kDoubleSize / 2)); // The result is returned on the stack. MemOperand return_operand = mantissa_operand; Register scratch1 = rbx; // Since we must use rcx for shifts below, use some other register (rax) // to calculate the result if ecx is the requested return register. Register result_reg = rax; // Save ecx if it isn't the return register and therefore volatile, or if it // is the return register, then save the temp register we use in its stead // for the result. Register save_reg = rax; __ pushq(rcx); __ pushq(scratch1); __ pushq(save_reg); __ movl(scratch1, mantissa_operand); __ Movsd(kScratchDoubleReg, mantissa_operand); __ movl(rcx, exponent_operand); __ andl(rcx, Immediate(HeapNumber::kExponentMask)); __ shrl(rcx, Immediate(HeapNumber::kExponentShift)); __ leal(result_reg, MemOperand(rcx, -HeapNumber::kExponentBias)); __ cmpl(result_reg, Immediate(HeapNumber::kMantissaBits)); __ j(below, &process_64_bits, Label::kNear); // Result is entirely in lower 32-bits of mantissa int delta = HeapNumber::kExponentBias + base::Double::kPhysicalSignificandSize; __ subl(rcx, Immediate(delta)); __ xorl(result_reg, result_reg); __ cmpl(rcx, Immediate(31)); __ j(above, &done, Label::kNear); __ shll_cl(scratch1); __ jmp(&check_negative, Label::kNear); __ bind(&process_64_bits); __ Cvttsd2siq(result_reg, kScratchDoubleReg); __ jmp(&done, Label::kNear); // If the double was negative, negate the integer result. __ bind(&check_negative); __ movl(result_reg, scratch1); __ negl(result_reg); __ cmpl(exponent_operand, Immediate(0)); __ cmovl(greater, result_reg, scratch1); // Restore registers __ bind(&done); __ movl(return_operand, result_reg); __ popq(save_reg); __ popq(scratch1); __ popq(rcx); __ ret(0);}

看上去很像汇编(编程的念念考景观按汇编来),本色上是c++函数,比如这行movl

__ movl(scratch1, mantissa_operand);

__是个宏,本色上是调用masm变量的函数(movl)

#define __ ACCESS_MASM(masm) #define ACCESS_MASM(masm) masm->

而movl的兑现是往pc_指针指向的内存写入mov教导及其操作数,并把pc_指针前进教导长度。

ps:一条条教导写下来,然后把内存权限改为可实施,这即是JIT的基快乐趣。

除了ASM和CPP的其它类型builtin王人通过调用CodeStubAssembler API(下称CSA)编写,这套API和之前先容ASM类型builtin时提到的“类汇编API”访佛,不同的是“类汇编API”径直产出原生代码,CSA产出的是turbofan的graph(IR)。CSA比起“类汇编API”的自制是无用每个平台各写一次。

关联词类汇编的CSA写起来照旧太辛苦了,于是V8提供了一个类javascript的高档话语:torque ,这话语最终会编译成CSA面孔的c++代码和V8其它C++代码一说念编译。

举例Array.isArray使用torque话语兑现如下:

namespace runtime {extern runtime ArrayIsArray(implicit context: Context)(JSAny): JSAny;} // namespace runtimenamespace array {// ES #sec-array.isarrayjavascript builtin ArrayIsArray(js-implicit context: NativeContext)(arg: JSAny): JSAny { // 1. Return ? IsArray(arg). typeswitch (arg) { case (JSArray): { return True; } case (JSProxy): { // TODO(verwaest): Handle proxies in-place return runtime::ArrayIsArray(arg); } case (JSAny): { return False; } }}} // namespace array

经过torque编译器编译后,会生成一段复杂的CSA的C++代码,底下截取一个片断

TNode Cast_JSProxy_1(compiler::CodeAssemblerState* state_, TNode p_context, TNode p_o, compiler::CodeAssemblerLabel* label_CastError) { // other code ... if (block0.is_used) { ca_.Bind(&block0); ca_.SetSourcePosition("../../src/builtins/cast.tq", 162); compiler::CodeAssemblerLabel label1(&ca_); tmp0 = CodeStubAssembler(state_).TaggedToHeapObject(TNode{p_o}, &label1); ca_.Goto(&block3); if (label1.is_used) { ca_.Bind(&label1); ca_.Goto(&block4); } } // other code ...}

和上头讲的Ldar字节码一样,这并不是跑在v8运行时的Array.isArray兑现。这段代码只运行在mksnapshot中,这段代码的居品是turbofan的IR。IR经过turbofan的优化编译青年景目的机器教导,然后dump到embedded.S汇编文献,底下才是实在跑在v8运行时的Array.isArray:

Builtins_ArrayIsArray:.type Builtins_ArrayIsArray, %function.size Builtins_ArrayIsArray, 214 .octa 0xd10043ff910043fda9017bfda9be6fe1,0x540003a9eb2263fff8560342f81e83a0 .octa 0x7840b063f85ff04336000182f9401be2,0x14000007d2800003540000607110907f .octa 0x910043ffa8c17bfd910003bff85b8340,0x35000163d2800020d2800023d65f03c0 .octa 0x540000e17102d47f7840b063f85ff043,0xf94da741f90003e2f90007ffd10043ff .octa 0x17ffffeef85c034017fffff097ffb480,0xaa1b03e2f9501f41d2800000f90003fb .octa 0x17ffffddf94003fb97ffb477aa0003e3,0x840000000100000002d503201f .octa 0xffffffff000000a8ffffffffffffffff .byte 0xff,0xff,0xff,0xff,0x0,0x1,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc

在这个过程中,JIT编译器turbofan相同干的是AOT的活。

1.3、builtin是怎样加载使用的

mksnapshot生成的包含系数builtin的居品embedded.S会和其他v8源码一说念编译成最终的v8库,embedded.S中声明了四个全局变量,分散是:

•v8_Default_embedded_blob_code_:起初化为第一个builtin的肇始位置(全部builtin紧凑的放在一个代码段里)

•v8_Default_embedded_blob_data_:指向一块数据,这块数据包含诸如各builtin相对v8_Default_embedded_blob_code_的偏移,builtin的长度等等信息

•v8_Default_embedded_blob_code_size_:系数builtin的总长度

•v8_Default_embedded_blob_data_size_:v8_Default_embedded_blob_data_数据的总长度

在v8/src/execution/isolate.cc中声明了几个extern变量,集聚embedded.S后v8/src/execution/isolate.cc就能援用到那几个变量:

extern "C" const uint8_t* v8_Default_embedded_blob_code_;extern "C" uint32_t v8_Default_embedded_blob_code_size_;extern "C" const uint8_t* v8_Default_embedded_blob_data_;extern "C" uint32_t v8_Default_embedded_blob_data_size_;

v8_Default_embedded_blob_data_中包含了各builtin的偏移,这些偏移组成一个数组,放在isolate的builtin_entry_table,数组下标是该builtin的陈设值。调用某builtin即是builtin_entry_table通过陈设值获取肇始地址调用。

2、snapshot

在V8引擎中,snapshot是指在启动时将部分或全部JavaScript堆内存的景况保存到一个文献中,以便在后续的启动中不错快速收复到这个景况。这个本领不错显赫减少V8引擎的启动时期,杰出是在大型应用法度中。

snapshot文献包含了以下几个部分:

•JavaScript堆的内存布局:包括了系数对象的地址、大小和类型等信息。

•JavaScript代码的字节码:包括了系数照旧编译的JavaScript函数的字节码。

•全局对象的景况:包括了全局对象的属性值、函数指针等信息。

•其他必要的景况:举例,垃圾回收器的景况、Just-In-Time (JIT) 编译器的缓存等。

当V8引擎启动时,若是存在灵验的Snapshot文献,V8会径直从这个文献中读取JavaScript堆的景况和字节码,而不需要重新解析和编译系数的JavaScript代码。这不错大幅度镌汰V8引擎的启动时期。V8的Snapshot本领有以下几个优点:

•快速启动:不错显赫减少V8引擎的启动时期,杰出是在大型应用法度中。

•低内存占用:由于部分或全部JavaScript堆的景况照旧被保存到文献中,是以在启动时不错检朴内存。

•悠闲性:Snapshot文献是由V8引擎生成的,保证了与引擎的兼容性和悠闲性。

若是不是交叉编译,snapshot生成照旧挺容易领悟的:v8对各类对象有作念了序列化和反序列化的营救,所谓生成snapshot,即是序列化,平凡会以context行为根来序列化。

mksnapshot制作快照不错输入一个非凡的剧本,也即是生成snapshot前允许实施一段代码,这段代码调用到的函数的编译恶果也会序列化下来,后续加载快照反序列化后等同于实施过了这剧本,就免去了编译过程,大大加速的启动的速率。

mksnapshot制作快照是通过调用v8::SnapshotCreator完成,而v8::SnapshotCreator提供了咱们输入外部数据的契机。若是唯有一个Context需要保存,用SnapshotCreator::SetDefaultContext就不错了,收复时径直v8::Context::New即可。若是有多于一个Context,不错通过SnapshotCreator::AddContext添加,它会复返一个索引,收复时输入索引即可收复到指定的归档。若是保存Context以外的数据,不错调用SnapshotCreator::AddData,然后通过Isolate或者Context的GetDataFromSnapshot接口收复。

//保存size_t context_index = snapshot_creator.AddContext(context, si_cb);//收复v8::Local context = v8::Context::FromSnapshot(isolate, context_index, di_cb).ToLocalChecked;

结合交叉编译时就会有个很蒙眬的场所:咱们前边提到mksnapshot在交叉编译时,JIT生成的builtin是目的机器教导,而js的运行得通过跑builtin来兑现(Ignition解析器每个教导即是一个builtin),这目的机器教导(比如arm64)怎样在腹地(比如linux 的x64)跑起来呢?mksnapshot为了兑现交叉编译中目的平台snapshot的生成,它作念了各类cpu(arm、mips、risc、ppc)的模拟器(Simulator)

通过稽察源码交叉编译时,mksnapshot会用一个目的机器的模拟器来跑这些builtin:

//src\common\globals.h#if !defined(USE_SIMULATOR)#if (V8_TARGET_ARCH_ARM64 && !V8_HOST_ARCH_ARM64)#define USE_SIMULATOR 1#endif// ...#endif//src\execution\simulator.h#ifdef USE_SIMULATOR Return Call(Args... args) { // other code ... return Simulator::current(isolate_)->template Call( reinterpret_cast(fn_ptr_), args...); }#else DISABLE_CFI_ICALL Return Call(Args... args) { // other code ... }#endif // USE_SIMULATOR

若是交叉编译,将会走USE_SIMULATOR分支。arm64将会调用到v8/src/execution/simulator-arm64.h, v8/src/execution/simulator-arm64.cc兑现的模拟器。上头Call的处理是把教导首地址赋值到模拟器的_pc寄存器,参数放寄存器,实施完教导从寄存器获取复返值。

六、V8移植的具体材干

一般咱们将负责编译的机器称为host,编译居品运行的目的机器称为target。

•本文使用的host机器是Mac M1 ,Xcode版块Version 14.2 (14C18)

•鸿蒙IDE版块:DevEco Studio NEXT Developer Beta5

•鸿蒙SDK版块是HarmonyOS-NEXT-DB5

•目的机器架构:arm64-v8a

若是要在Mac M1上交叉编译鸿蒙 arm64的builtin,材干如下:

•调用腹地编译器,编译一个Mac M1版块mksnapshot可实施法度

•实施上述mksnapshot生成鸿蒙平台arm64教导并dump到embedded.S

•调用鸿蒙sdk的器具链,编译集聚embedded.S和v8的其它代码,生成能在鸿蒙arm64上使用的v8库

1.首先装配cmake及ninja构建器具

鸿蒙sdk自带构建器具咱们不错将它们加入环境变量中使用

2.编写交叉编译V8到鸿蒙的CMakeList.txt

统共有1千多行,部分CMakeList.txt片断:

3.使用host本机的编译器具链编译

$ mkdir build $ cd build $ cmake -G Ninja .. $ ninja 或者 cmake --build .

首先创建一个编译目次build,掀开build实施cmake -G Ninja .. 生成针对ninja编译需要的文献。

底下是适度台打印的器具链树立信息,使用的是Mac腹地xcode的器具链:

build文献夹下生成以下文献:

其中CMakeCache.txt是一个由CMake生成的缓存文献,用于存储CMake在树立过程中所作念的遴荐和决策。它是证据你的技俩的CMakeLists.txt文献和系统环境来生成一个起初的CMakeCache.txt文献。这个文献包含了系数可树立的选项及其默许值。

build.ninja文献是Ninja的主要输入文献,包含了技俩的系数构建规矩和依赖关系。

这个文献的内容是Ninja的语法,形容了怎样从源文献生成目的文献。它包括了以下几个部分:

•规矩:界说了怎样从源文献生成目的文献的规矩。举例,编译C++文献、集聚库等。

•构建目的:列出了技俩中系数需要构建的目的,包括可实施文献、静态库、动态库等。

•依赖关系:形容了各个构建目的之间的依赖关系。Ninja会证据这些依赖关系来信托构建的礼貌。

•变量:界说了一些Ninja使用的变量,举例编译器、编译选项等。

然后实施cmake --build . 或者 ninja

稽察build文献夹下生成的居品:

其中红框中的三个可实施文献是在编译过程中生成,同期还会在编译过程中实施。bytecode_builtins_list_generator主要生成是字节码对应builtin的生成代码。torque负责将.tq后缀的文献(使用torque话语编写的builtin)编译成CSA类型builtin的c++源码文献。

torque编译.tq文献生成的c++代码在torque-generated目次中:

bytecode_builtins_list_generator实施生成字节码函数列表不才面庞录中:

mksnapshot则集聚这些代码并实施,实施时间会在内置的对应架构模拟器中运行v8,最毕生成host平台的buildin汇编代码——embedded.S和snapshot(context的序列化对象)——snapshot.cc。它们跟随其他v8源代码一说念编译生成最终的v8静态库libv8_snapshot.a。现在build目次中照旧编译出host平台的完好v8静态库及敕令行调试器具d8。

mksnapshot法度本人的编译生成及实施在CMakeList.txt中的树立代码如下:

4.使用鸿蒙SDK的编译器具链编译

因为在编译target平台的v8时中间生成的bytecode_builtins_list_generator,torque,mksnapshot可实施文献是针对target架构的无法在host机器上实施。是以首先需要把上头在host平台生成的可实施文献拷贝到/usr/local/bin,这样在编译target平台的v8过程中实施这些中间法度时会找到 /usr/local/bin下的可实施文献正确的实施生成针对target的builtin和snapshot快照。

$ cp bytecode_builtins_list_generator torque mksnapshot /usr/local/bin$ mkdir ohosbuild #创建新的鸿蒙v8的编译目次$ cd ohosbuild#使用鸿蒙提供的器具链文献 $ cmake -DOHOS_STL=c++_shared -DOHOS_ARCH=arm64-v8a -DOHOS_PLATFORM=OHOS -DCMAKE_TOOLCHAIN_FILE=/Applications/DevEco-Studio.app/Contents/sdk/HarmonyOS-NEXT-DB5/openharmony/native/build/cmake/ohos.toolchain.cmake -G Ninja ..$ ninja 或者 cmake --build .

实施第一步cmake树立后适度台的信息不错看到,使用了鸿蒙的器具链

实施完成后ohosbuild文献夹下生成了鸿蒙平台的v8静态库,不错修改CMakeList.txt树立合成一个.a或者生成.so。

七、鸿蒙工程中使用v8库

1.新建native c++工程

2.导入v8库

将v8源码中的include目次和上头编译生成的.a文献放入cpp文献夹下

3.修改cpp目次下CMakeList.txt文献

竖立c++法度17,集聚v8静态库

4.添加napi行为测试使用v8

底下是简便的demo

导出c++行为

arkts侧调用c++行为

运行稽察恶果:

八、JS引擎的发展趋势

跟着物联网的发展,东说念主们对IOT斥地(如智高腕表)的使用越来越多。若是但愿把JS应用到IOT范围,势必需要从JS引擎角度去进行优化,只是去作念表层的框架成效甚微。因为对于IOT硬件来说,CPU、内存、电量王人是需要省着点用的,不是每一个智能家电王人需要装一个骁龙855。那怎样不错基于V8引擎进行校正来进一步普及JS的实施性能呢?

•使用TypeScript编程,遵命严格的类型化编程规矩;

•构建的时候将TypeScript径直编译为Bytecode,而不是生成JS文献,这样运行的时候就省去了Parse以及生成Bytecode的过程;

•运行的时候,需要先将Bytecode编译为对应CPU的汇编代码;

•由于接管了类型化的编程景观,成心于编译器优化所生成的汇编代码,省去了好多非凡的操作;

基于V8引擎来兑现,本领上应该是可行的:

•将Parser以及Ignition拆分出来,用于构建阶段;

•删掉TurboFan处理JS动态脾性的联系代码;

这样不错将JS引擎简化好多,一方面不再需要parse以及生成bytecode,另一方面编译器不再需要因为JavaScript动态脾性作念好多非凡的使命。因此不错减少CPU、内存以及电量的使用,优化性能,唯独的问题是必须使用严格的TS语法进行编程。

Facebook的Hermes差未几即是这样干的,只是它莫得条目用TS编程。

如今鸿蒙原生的ETS引擎Panda亦然这样干的,它条目使用ets语法,其实是基于TS只不外作念了愈加严格的类型及语法舍弃(铁心了更多的动态脾性),进一步普及js的实施性能。

将V8移植到鸿蒙系统是一个雄壮的镶嵌式限度使命,波及交叉编译、CMake、CLang、Ninja、C++、torque等各类学问,天然咱们履历了雄壮挑战并掌抓了V8移植本领,但出于应用包大小、悠闲性、兼容性、负责本钱等维度详细商量,若是华为系统能内置V8,对Roma框架及业界系数依赖JS造谣机的跨端框架王人是一件意旨潜入的事情,通过和华为持续换取,鸿蒙从API11版块提供了一个内置的JS引擎,它本色上是基于v8的封装,并提供了一套c-api接口。

若是不想用c-api况且不商量包大小的问题仍然不错我方编译一个沉静的v8引擎镶嵌APP,径直使用v8面向对象的C++ API。

Roma框架是一个波及JavaScript、C&C++、Harmony、iOS、Android、Java、Vue、Node、Webpack等繁密范围的详细处分决议成人电影院,咱们有各个范围优秀的小伙伴共同前行,公共若是想深入了解某个范围的具体兑现,不错随时留言交流~