.Net8頂級技術:邊界檢查之IR解析(二)

前言

IR技術應用在各個編程語言當中,它屬於JIT的核心部分,確實有點點麻煩。但部分基本明瞭。本篇通過.Net8裏面的邊界檢查的小例子瞭解下。前情提要,看這一篇之前建議看看前一篇:點擊此處,以便於理解。


概括

1.前奏
先上C#代碼:

[MethodImpl(MethodImplOptions.NoInlining)]
private static bool Test(int[] array)
{
   for (int i = 0; i < 0x12345; i++)
   {
      if (array[i] == 42)
      {
         return true;
      }
   }
  return false;
}

Test函數經過Roslyn編譯成IL代碼之後,會被JIT導入及操作變成IR。

BBnum BBid ref try hnd preds           weight    lp [IL range]     [jump]      [EH region]         [flags]
-----------------------------------------------------------------------------------------------------------------------------------------
BB01 [0007]  1                             1       [???..???)-> BB04 ( cond )                     internal 
BB02 [0001]  2       BB01,BB03             4     0 [004..00B)-> BB05 ( cond )                     i Loop idxlen bwd bwd-target align 
BB03 [0003]  1       BB02                  4     0 [00D..019)-> BB02 ( cond )                     i bwd 
BB04 [0005]  2       BB01,BB03             0.50    [019..01B)        (return)                     i 
BB05 [0002]  1       BB02                  0.50    [00B..00D)        (return)                     i 
-----------------------------------------------------------------------------------------------------------------------------------------

可以看到IL被分割成了五個BB(basic block).注意表格的BBnum和jump列。BB01的BBnum就是BB01,它的jump是BB04。爲啥是BB04?因爲BB01的IR表示的是如果(i>=0x12345),則跳轉到BB04,也就是BB01的正常邏輯。下面看下這個五個BB.

------------ BB01 [???..???) -> BB04 (cond), preds={} succs={BB02,BB04}
***** BB01
STMT00006 ( 0x011[E-] ... ??? )
     (  7,  9) [000038] -----------                         *  JTRUE     void  
     (  5,  7) [000039] J------N---                         \--*  GE        int   
     (  3,  2) [000040] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  4) [000041] -----------                            \--*  CNS_INT   int    0x12345

------------ BB02 [004..00B) -> BB05 (cond), preds={BB01,BB03} succs={BB03,BB05}
***** BB02
STMT00002 ( 0x004[E-] ... 0x009 )
               [000013] ---XG+-----                         *  JTRUE     void  
               [000012] N--XG+-N-U-                         \--*  EQ        int   
               [000034] ---XG+-----                            +--*  COMMA     int   
               [000026] ---X-+-----                            |  +--*  BOUNDS_CHECK_Rng void  
               [000008] -----+-----                            |  |  +--*  LCL_VAR   int    V01 loc0         
               [000025] ---X-+-----                            |  |  \--*  ARR_LENGTH int   
               [000007] -----+-----                            |  |     \--*  LCL_VAR   ref    V00 arg0         
               [000035] n---G+-----                            |  \--*  IND       int   
               [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
               [000032] -----+-----                            |        \--*  ADD       byref 
               [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0         
               [000031] -----+-----                            |           \--*  ADD       long  
               [000029] -----+-----                            |              +--*  LSH       long  
               [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
               [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
               [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
               [000030] -----+-----                            |              \--*  CNS_INT   long   16
               [000011] -----+-----                            \--*  CNS_INT   int    42

------------ BB03 [00D..019) -> BB02 (cond), preds={BB02} succs={BB04,BB02}
***** BB03
STMT00003 ( 0x00D[E-] ... 0x010 )
               [000018] -A---+-----                         *  ASG       int   
               [000017] D----+-N---                         +--*  LCL_VAR   int    V01 loc0         
               [000016] -----+-----                         \--*  ADD       int   
               [000014] -----+-----                            +--*  LCL_VAR   int    V01 loc0         
               [000015] -----+-----                            \--*  CNS_INT   int    1

***** BB03
STMT00001 ( 0x011[E-] ... 0x017 )
     (  7,  9) [000006] -----------                         *  JTRUE     void  
     (  5,  7) [000005] J------N---                         \--*  LT        int   
     (  3,  2) [000003] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  4) [000004] -----------                            \--*  CNS_INT   int    0x12345

------------ BB04 [019..01B) (return), preds={BB01,BB03} succs={}
***** BB04
STMT00005 ( 0x019[E-] ... 0x01A )
               [000022] -----+-----                         *  RETURN    int   
               [000037] -----+-----                         \--*  CNS_INT   int    0

------------ BB05 [00B..00D) (return), preds={BB02} succs={}
***** BB05
STMT00004 ( 0x00B[E-] ... 0x00C )
               [000020] -----+-----                         *  RETURN    int   
               [000036] -----+-----                         \--*  CNS_INT   int    1

preds表示能在邏輯上執行到當前塊的所有快,succs表示當前語句邏輯能達到的BB塊。舉個例子:比如BB01,首先看下這條IR表示的如果(i>=0x12345),則跳轉到BB04,也就是直接返回0。因爲邏輯是索引大於了循環的最大次數,是不合理的。如果(i<0x12345),則跳轉到BB02,也就是判斷(array[i]是否等於42)。上面BB01的predes爲空,則表示沒有邏輯能達到這條語句。它的succs爲BB02和BB04,跟上面的推測吻合。其它依次類推。

2.BB的IR表示
通過上面的BB01到BB05的觀察,得知它們分別表示如下:
一:BB01

if(i>=0x12345)

二:BB02

if(array[i]==42)

三:BB03

i=i+1;
if(i<0x12345)

四:BB04

return 0

五:BB05

return 1

以上循環被分割成了五個BB。它的實際邏輯如下:

if(i>=0x12345)
{
  return flase;
}
else
{
  for(i<0x12345;i++)
  {
    if(array[i]==42)
    {
      return true;
    }
  }
  return flase;
}

所以呢,實際是示例的for循環,被分解成了上面的代碼。但是還沒完,爲了確保這個array[i]不會出現內存訪問的錯誤,BB02裏面有個BOUNDS_CHECK_Rng的邊界檢查技術,它會判斷array[i]裏的i索引是否查過array.length的長度,因爲在for循環裏面,所以每次都會判斷,會增加相應的開銷。爲了達到最優的效果,.Net8會去掉這開銷。那麼應該怎麼做呢?繼續看。
JIT先增加BB06,BB07,BB08,BB09四個塊,然後把BOUNDS_CHECK_Rng給去掉。
去掉前後對比如下。
去掉前:

          [000013] ---XG+-----                         *  JTRUE     void  
               [000012] N--XG+-N-U-                         \--*  EQ        int   
               [000034] ---XG+-----                            +--*  COMMA     int   
               [000026] ---X-+-----                            |  +--*  BOUNDS_CHECK_Rng void  
               [000008] -----+-----                            |  |  +--*  LCL_VAR   int    V01 loc0         
               [000025] ---X-+-----                            |  |  \--*  ARR_LENGTH int   
               [000007] -----+-----                            |  |     \--*  LCL_VAR   ref    V00 arg0         
               [000035] n---G+-----                            |  \--*  IND       int   
               [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
               [000032] -----+-----                            |        \--*  ADD       byref 
               [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0         
               [000031] -----+-----                            |           \--*  ADD       long  
               [000029] -----+-----                            |              +--*  LSH       long  
               [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
               [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
               [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
               [000030] -----+-----                            |              \--*  CNS_INT   long   16
               [000011] -----+-----                            \--*  CNS_INT   int    42

去掉後:

  [000013] ----G+-----                         *  JTRUE     void  
               [000012] N---G+-N-U-                         \--*  EQ        int   
               [000034] ----G+-N---                            +--*  COMMA     int   
               [000026] -----+-----                            |  +--*  NOP       void  
               [000035] n---G+-----                            |  \--*  IND       int   
               [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
               [000032] -----+-----                            |        \--*  ADD       byref 
               [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0         
               [000031] -----+-----                            |           \--*  ADD       long  
               [000029] -----+-----                            |              +--*  LSH       long  
               [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
               [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
               [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
               [000030] -----+-----                            |              \--*  CNS_INT   long   16
               [000011] -----+-----                            \--*  CNS_INT   int    42

然後再新增BB10,BB11,BB12,BB13四個BB塊。這些BB塊如下所示:

------------ BB01 [???..???) -> BB12 (cond), preds={} succs={BB02,BB12}

***** BB01
STMT00006 ( 0x011[E-] ... ??? )
     (  7,  9) [000038] -----------                         *  JTRUE     void  
     (  5,  7) [000039] J------N---                         \--*  GE        int   
     (  3,  2) [000040] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  4) [000041] -----------                            \--*  CNS_INT   int    0x12345

------------ BB02 [???..???), preds={BB01} succs={BB03}

------------ BB03 [???..???) -> BB09 (cond), preds={BB02} succs={BB04,BB09}

***** BB03
STMT00010 ( ??? ... ??? )
     (  7,  6) [000072] -----------                         *  JTRUE     void  
     (  5,  4) [000071] J------N---                         \--*  EQ        int   
     (  3,  2) [000069] -----------                            +--*  LCL_VAR   ref    V00 arg0         
     (  1,  1) [000070] -----------                            \--*  CNS_INT   ref    null

------------ BB04 [???..???) -> BB09 (cond), preds={BB03} succs={BB05,BB09}

***** BB04
STMT00011 ( ??? ... ??? )
     (  7,  6) [000076] -----------                         *  JTRUE     void  
     (  5,  4) [000075] J------N---                         \--*  LT        int   
     (  3,  2) [000073] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  1) [000074] -----------                            \--*  CNS_INT   int    0

------------ BB05 [???..???) -> BB09 (cond), preds={BB04} succs={BB06,BB09}

***** BB05
STMT00012 ( ??? ... ??? )
     (  9, 11) [000081] ---X-------                         *  JTRUE     void  
     (  7,  9) [000080] J--X---N---                         \--*  LT        int   
     (  5,  4) [000079] ---X-------                            +--*  ARR_LENGTH int   
     (  3,  2) [000078] -----------                            |  \--*  LCL_VAR   ref    V00 arg0         
     (  1,  4) [000077] -----------                            \--*  CNS_INT   int    0x12345

------------ BB06 [004..00B) -> BB13 (cond), preds={BB05,BB07} succs={BB07,BB13}

***** BB06
STMT00002 ( 0x004[E-] ... 0x009 )
               [000013] ----G+-----                         *  JTRUE     void  
               [000012] N---G+-N-U-                         \--*  EQ        int   
               [000034] ----G+-N---                            +--*  COMMA     int   
               [000026] -----+-----                            |  +--*  NOP       void  
               [000035] n---G+-----                            |  \--*  IND       int   
               [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
               [000032] -----+-----                            |        \--*  ADD       byref 
               [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0         
               [000031] -----+-----                            |           \--*  ADD       long  
               [000029] -----+-----                            |              +--*  LSH       long  
               [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
               [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
               [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
               [000030] -----+-----                            |              \--*  CNS_INT   long   16
               [000011] -----+-----                            \--*  CNS_INT   int    42

------------ BB07 [00D..019) -> BB06 (cond), preds={BB06} succs={BB08,BB06}

***** BB07
STMT00003 ( 0x00D[E-] ... 0x010 )
               [000018] -A---+-----                         *  ASG       int   
               [000017] D----+-N---                         +--*  LCL_VAR   int    V01 loc0         
               [000016] -----+-----                         \--*  ADD       int   
               [000014] -----+-----                            +--*  LCL_VAR   int    V01 loc0         
               [000015] -----+-----                            \--*  CNS_INT   int    1

***** BB07
STMT00001 ( 0x011[E-] ... 0x017 )
     (  7,  9) [000006] -----------                         *  JTRUE     void  
     (  5,  7) [000005] J------N---                         \--*  LT        int   
     (  3,  2) [000003] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  4) [000004] -----------                            \--*  CNS_INT   int    0x12345

------------ BB08 [???..???) -> BB12 (always), preds={BB07} succs={BB12}

------------ BB09 [???..???), preds={BB03,BB04,BB05} succs={BB10}

------------ BB10 [004..00B) -> BB13 (cond), preds={BB09,BB11} succs={BB11,BB13}

***** BB10
STMT00007 ( 0x004[E-] ... ??? )
               [000042] ---XGO-----                         *  JTRUE     void  
               [000043] N--XGO-N-U-                         \--*  EQ        int   
               [000044] ---XGO-----                            +--*  COMMA     int   
               [000045] ---X-O-----                            |  +--*  BOUNDS_CHECK_Rng void  
               [000046] -----------                            |  |  +--*  LCL_VAR   int    V01 loc0         
               [000047] ---X-------                            |  |  \--*  ARR_LENGTH int   
               [000048] -----------                            |  |     \--*  LCL_VAR   ref    V00 arg0         
               [000049] n---GO-----                            |  \--*  IND       int   
               [000050] -----O-----                            |     \--*  ARR_ADDR  byref int[]
               [000051] -----------                            |        \--*  ADD       byref 
               [000052] -----------                            |           +--*  LCL_VAR   ref    V00 arg0         
               [000053] -----------                            |           \--*  ADD       long  
               [000054] -----------                            |              +--*  LSH       long  
               [000055] ---------U-                            |              |  +--*  CAST      long <- uint
               [000056] -----------                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
               [000057] -------N---                            |              |  \--*  CNS_INT   long   2
               [000058] -----------                            |              \--*  CNS_INT   long   16
               [000059] -----------                            \--*  CNS_INT   int    42

------------ BB11 [00D..019) -> BB10 (cond), preds={BB10} succs={BB12,BB10}

***** BB11
STMT00008 ( 0x00D[E-] ... ??? )
               [000060] -A---------                         *  ASG       int   
               [000061] D------N---                         +--*  LCL_VAR   int    V01 loc0         
               [000062] -----------                         \--*  ADD       int   
               [000063] -----------                            +--*  LCL_VAR   int    V01 loc0         
               [000064] -----------                            \--*  CNS_INT   int    1

***** BB11
STMT00009 ( 0x011[E-] ... ??? )
     (  7,  9) [000065] -----------                         *  JTRUE     void  
     (  5,  7) [000066] J------N---                         \--*  LT        int   
     (  3,  2) [000067] -----------                            +--*  LCL_VAR   int    V01 loc0         
     (  1,  4) [000068] -----------                            \--*  CNS_INT   int    0x12345

------------ BB12 [019..01B) (return), preds={BB01,BB08,BB11} succs={}

***** BB12
STMT00005 ( 0x019[E-] ... 0x01A )
               [000022] -----+-----                         *  RETURN    int   
               [000037] -----+-----                         \--*  CNS_INT   int    0

------------ BB13 [00B..00D) (return), preds={BB06,BB10} succs={}

***** BB13
STMT00004 ( 0x00B[E-] ... 0x00C )
               [000020] -----+-----                         *  RETURN    int   
               [000036] -----+-----                         \--*  CNS_INT   int    1

-------------------------------------------------------------------------------------------------------------------

3.BB塊分析
通過去掉的邊界檢查,進行的優化之後。新增了7個BB塊,總共有13個BB塊。那麼這些BB幹嘛的呢?實際上就是爲了去掉邊界檢查(因爲在for循環裏,每次都要判斷),而確保內存array[i]在正確內存範圍內。逐一來看下:
BB01:

if(i>=0x12345)判斷索引是否大於循環最大值

BB02

BB03

if(array==null) //這裏是判斷數組的地址是否等於0

BB04

if(i<0)判斷索引是否小於0

BB05

if(array.length<0x12345)判斷數組長度是否小於循環最大數0x12345

BB06

if(array[i]==42)

BB07

i=i+1索引自增

BB08

BB09

BB10

if(i<array.length) //這裏跟上面的BB06一樣,但是多了邊界檢查。BB06去掉,這裏沒去掉。是因爲這裏需要邊界檢查,而BB06不需要。一個快速路徑,一個慢速路徑。
if(array[i]==42)

BB11

i=i+1

BB12

return 0

BB02

return 1

它實際邏輯是:

if(i<0x12345 && array!= null && i>0 && array.Length >= 0x12345 )//再去掉邊界檢查之後的優化裏,這進行大量的檢查,確保array[i],在正確內存範圍內。
{
    for (int i = 0; i < 0x12345; i++)
    {
        if (array[i] == 42) 不檢查邊界,因爲上面的if檢查過了
        {
            return true;
        }
    }
}
else  //如果上面的if有一個條件不符合,則進行邊界檢查。優化不成功
{
   for (int i = 0; i < 0x12345; i++)
    {
        if (array[i] == 42) 這裏需要邊界檢查也就是BOUNDS_CHECK_Rng
        {
            return true;
        }
    }
}

結尾

作者:江湖評談
歡迎關注公衆號,第一時間首發分享技術文章。
image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章