美文网首页
JSCore运行时一些概念

JSCore运行时一些概念

作者: FingerStyle | 来源:发表于2020-03-28 18:31 被阅读0次

    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),当前帧的局部变量也会被覆盖。

    相关文章

      网友评论

          本文标题:JSCore运行时一些概念

          本文链接:https://www.haomeiwen.com/subject/rmusuhtx.html