[WebKit] JavaScriptCore解析--高階篇(二) 型別推導(Type Inference)
型別推導是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絕大多數的型別檢查通常是在載入堆資料時發生的。
深入閱讀:
原文地址: http://trac.webkit.org/wiki/JavaScriptCore
系列索引: