类型推导是DFG JIT最重要的一个基础,WebKit官网对此做了一点解释,翻译如下做为学习参考。
Type inference通过profiling values来做到的,先是预测对哪些类型操作进行分析,再添加类型检查,最后基于类型检查的结果建立类型统计数据。
用下面的例子来说明这个过程:
o.x * o.x + o.y * o.y
其中o是一个对象,x和y是它的属性,它们不是访问器(accessor),只是一般的属性。我们也可以说这两个属性值会返回double类型数值,但也有时会返回整型数据。JavaScriptCore通过用int32来表现整型数据,而不是用double类型。
1. 对于表达式o.x,先要检查o有没有任何特殊的访问属性的处理。比如是一个DOM对象,那么它的属性访问操作就不是可见的。如果没有特殊的处理,JSC会在对象的属性中找出名字为'x'的属性。对象都一个将字串映射到值或访问器(accessor)的表,如果所找的字串指向一个访问器,那么这个访问器就会被调用,如果指一个值,那就直接返回。如'x'没能在对象o中找到,就到它的原型中依次找下去。类型推导对这部分操作没有什么作用。
2. 二元的相乘操作,如'o.x * o.x',首先检查操作数(operands)的类型。如果操作数是个对象,就是调用它的valueOf方法。如果操作数是个字串,就要先转换为数值。当两个操作数已经被转为等价的数值(如果可以的话)后,JSC会再检查它们是不是全是整数,如果是则执行整型的相乘。如果溢出,就会用double类型的相乘再算一次。如果有一个操作数是double数值,它们都会转为double型,并执行double型的相乘操作。因此'o.x*o.x'所返回的结果,要么是整数,要么是浮点数(double)。
3. 对于表达式'o.x*o.x + o.y*o.y'和上面类似,只是要多考虑一下操作数是字串的情况,中间的'+'可能会是字串合并的操作。不过在这里,我们可以很容易地确定,返回值仍然要么是整数,要么是浮点数(double)。
JSC的类型推导就是如果我可以猜到输入的数据类型,就可以给出数值操作后最可能返回的类型,以及其路径。这里使用了一系列归纳步骤, 如果我们可以预计它的输入就可以预计它的输出。对于一些非局部变量,比如从堆中取出的值(如o.x),和函数返回值,我们都称为堆值(heap values), 所有将heap values赋给局部变量的操作都视为堆操作(heap oerpations). 类型预测使用到了value profiling, LLInt和Baseline JIT都会记录在任何heap oerpation中记录下最常用的数值。每个堆操作都有一个对应的value profile bucket, 每一个value profile bucket都会存储一个最近值。
简单的看,JSC的类型推导就是把value profile中最常用的值的类型作为其以后要使用的类型。这样所有的变量都变成是可以进行类型预测的了。 事实上,在每个value profile中还有第二个内容,它是能包括已出现的一部分数据值的数据类型。这个类型使用了SpeculatedType(或SpecType)类型系统,实现在SpeculatedType.h中。每个value profile中这个类型会先设为SpecNone(就是没有数据)。当Baseline JIT执行次数超出阀值(JIT.cpp中的JIT::emitOptimizationCheck),它会生成一个新的类型,可以同时让最后一次的类型和最常用的数值符合这个类型。它或许会触发DFG,也可以让Baseline再多执行几次。当进入DFG JITF后,每个value profile通常会有一个这样可以包容多个不同值的类型。
SpecTypes之所以可行,源自于函数中的操作和变量都会使用标准的前置数据流( forward data flow )规范,实现了所谓的流程无关的不动点(fixpoint). 这是DFG编译的第一个阶段,由Baseline JIT基于执行次数决定是否激活(DFGPredictionPropagationPhase.cpp)。
在每个使用了预测数据类型的函数中,我们插入了基于预测的类型检查操作。如果类型检查失败,就会回退到Baseline JIT中。下面分解下'a+b'的附加操作是如何执行的, 假设a和b都被预测为SpecInt32类型:
check if a is Int32 -> 否则 OSR exit to Baseline JIT
check if b is Int32 -> 否则 OSR exit to Baseline JIT
result = a + b // integer addition
check if 溢出 -> 否则 OSR exit to Baseline JIT
操作执行完成后,我们可以知道:
- 'a' 是整数.
- 'b' 是整理.
- 结果也是整数.
后面的操作就不用再检查'a' or 'b'的类型了,这有一个消除类型检查的操作,是通过第二个数据流分析来完成的,被称为DFG CFA (DFGCFAPhase.cpp 和 DFGAbstractState.cpp) . 它也实现了稀疏条件传播(sparse conditional constant propagation),让它可以有能力像确定类型一样确定某些值是不是常量。
对于表达式 'o.x * o.x + o.y * o.y',只需要在取o.x和o.y时进行类型检查。然后我们就知道它们的值是doubles, 就是只需触发double的相乘和相加。DFG绝大多数的类型检查通常是在加载堆数据时发生的。
深入阅读:
Fast and Precise Hybrid Type Inference for JavaScript
Type Inference in SpiderMonkey
原文地址: http://trac.webkit.org/wiki/JavaScriptCore
转载请注明出处:http://blog.csdn.net/horkychen
系列索引:
高级篇(一) SSA (static single assignment)
高级篇(三) Register Allocation & Trampoline