JS运行时
Share/Structure: JS对象所包含的属性集合的一个全局唯一标识,类似C语言里面的结构体。增加或减少属性会导致Shape的变化,Shape之间是有继承关系的(transition chains). Shape中保存了每个属性的offset, 使用Shape可以减少因为存储同样属性集合的变量所占用的内存,减少冗余数据,并且通过offset直接获取属性和方法,访问效率会更高。
内联缓存: 对一些经常查询的属性和方法对应的的Shape的offset进行缓存,在下次读取时跳过对shape的查找过程,直接定位属性。
对开发者的建议:
1.尽量以相同方式初始化对象,因为这样会生成较少的 Shapes。
2.不要混淆对象的 propertyKey 与数组的下标,虽然都是用类似的结构存储,但 JS 引擎对数组下标做了额外优化。
3.不要使用argument.callee.caller,这个会导致调用的参数类型和方法都不确定, JS引擎没办法做内联缓存优化
https://stackoverflow.com/questions/103598/why-was-the-arguments-callee-caller-property-deprecated-in-javascript
Shape如何获取:
LLInt 和 Baseline JIT 会从函数参数或者来自堆的读取来收集每个变量的值信息,根据这些信息来假设一个类型
关于数字类型的编码
参考:https://liveoverflow.com/the-butterfly-of-jsobject-browser-0x02/
Pointer { 0000:PPPP:PPPP:PPPP
/ 0001:****:****:****
Double { ...
\ FFFE:****:****:****
Integer { FFFF:0000:IIII:IIII
False: 0x06
True: 0x07
Undefined: 0x0a
Null: 0x02
JS 的 Number 在生成时,会把值编码到它的地址里,解析时也是靠解码地址来解值,可以自己实现这个过程避免 JSLock,除此之外 JS 里的 undefined,null 都是固定值。TypedArray 也有个好处,初始化后它底层的地址不会改变,可以靠地址偏移还高效去数据。 类似iOS 的tagged number 。每一种类型的number都有固定的地址区间,不会重叠(通过前面几位来标识),这也导致了只有53个bit可以用来表示数字,当js数字超过16位(2^53)时,就会丢失精度。
因为 JSNumber 不会被 GC,且传递相对高效,只需要编解码地址,所以 JSObject 我们可以设置一个 JSNumber 作为句柄,JS 和 Native 靠这个句柄从缓存中取对象,不用经过 JS 虚拟机。
RN里面js和native交互使用了number类型作为moduleID,而不是string类型,应该也是有这方面的考虑。
关于js的定时器和promise
定时器没找到源码,应该是通过把回调函数(microtask)添加到异步队列中(通过runloop的timer事件唤醒),在同步队列执行完成后执行的
promise:采用于timer类似的方式放到异步队列中,只是不定时
JavaScriptCore runtime类说明
VM : JS虚拟机,超级大的一个对象,解释并执行JS,被globalObject所引用,也引用了很多其他对象(如调用栈的栈顶、最近一次的异常、入口作用域、运行循环、字节码的缓存等)
methodTable 一个结构体,里面定义了很多函数指针, 可以获取和操作js对象里面的属性、方法
classInfo 一个结构体,里面有类名、父类的指针、方法表等
JSCell Structure和JSObject的基类,对methodTable和classInfo的一些封装
JSObject 表示一个js的对象,继承自JSCell,实现了很多js对象的基本操作方法
Structure 表示JS对象的结构,类似V8中的Shape,用来快速查找对象的属性,属性不一样,structure ID也不一样
Bufferfly JS对象的封装,其结构为各种属性(第一个是structureId)+ 长度+ 内部元素, 其指针指向的不是结构体开始的地址,而是中间的地址(内部元素开始的位置)
StringObject、NumberObject、JSFunction,JSArray 等等: 对JS里面相应类型变量的封装, 里面有一些操作Object的方法如: defineOwnProperty ,put ,create, getOwnPropertyDescriptor等
ArrayProtoType/StringProtoType等:对JS标准库函数的实现,比如array的reverse(),string 的split()
LiteralParser : 对JSON的解析
JSWarpperValue : JS包装对象,如StringObject、NumberObject、BooleanObject、BitIntObject,是对原始类型的封装
JSONObject: JSON对象,实现了stringify方法,里面会对JS包装对象解包,转成对应字符串,注意bigInt不能被转换
ObjcCallbackFunction: 通过runtime把OC的方法转成JS的方法
TypedArray: 把js Array中的 number类型的数据转成了float、int 、uint等类型,跟原生的数据类型对应
Inspector:用来调试用的,即safari的debug功能
eventLoop : 事件循环,对应于iOS的runloop
CodeBlock: 代码片段,是函数体经过词法、语法解析后生成的字节码集合
==================调用栈相关==================
ScriptCallStack: 普通的JS调用栈,用vector容器保存了最多200个栈帧(ScriptCallFrame)
ScriptCallStackFactory: 调用栈的工厂类,包含了4种调用栈:普通调用栈、console用的调用栈、异常调用栈、脚本参数。 异常调用栈的信息基本都从异常对象(JSC::Exception)中获取
ScriptCallFrame: JS栈帧,里面有行号、列号、函数名测、脚本名称等信息 ,可以转换为CallFrame类型
CallFrame: 原始的栈帧,继承于Register. 里面包含了作用域(scope)、调用者(callee)的引用。内部有一个结构体CallerFrameAndPC,包含了调用者的栈帧(callerFrame)和返回值的地址(returnPC),以及对应的寄存器数量
CallFrameShuffler: 不知道怎么翻译,看源码主要是用来生成栈帧的, 根据调用函数前保存调用者地址、变量等到虚拟寄存器,调用完成后再从虚拟寄存器恢复地址。虚拟寄存器分为两类:
1.FPR: 函数参数域(Function Parameter Register), 这个区域的大小是变化的,当调用者传递给被调用者的参数少于8个时,用GPR3-GPR10这8个寄存器就行,被调用者的栈帧中就可不要这个区域;但如果传递的参数多于8个时就需要这个区域。
2.GPR:通用寄存器(General Parameter Register),当需要保存GPR寄存器中的一个寄存器GPRx时,就需要把从GPRx-GPR31的值都保存到堆栈帧中。
可以看出,大部分情况使用的是GPR。
关于栈帧的结构,可以看这个 https://www.cnblogs.com/qinfengxiaoyue/p/3523166.html
生成栈帧有两个方法:尾调用(prepareForTailCall)和 常规调用( prepareForSlowPath)。
prepareForTailCall 就是正常的往栈顶扩展,不会覆盖调用者的栈帧。但是prepareForSlowPath 会恢复保存调用者的寄存器(callee save registers),当前帧的局部变量也会被覆盖。
网友评论