1. 程式人生 > >GCC原始碼分析(五)——指令生成

GCC原始碼分析(五)——指令生成

原文連結:http://blog.csdn.net/sonicling/article/details/8246231


一、前言

  又有好久沒寫了,的確很忙。前篇介紹了GCC的pass格局,它是GCC中間語言部分的核心架構,也是貫穿整個編譯流程的核心。在完成優化處理之後,GCC必須做的最後一步就是生成最後的編譯結果,通常情況下就是彙編檔案(文字或者二進位制並不重要)。

  前面也講到了,GCC中間語言的核心資料結構是GENERIC、GIMPLE和RTL。其中的RTL就是和指令緊密相關的一種結構,它是指令生成的起點。

二、RTL和INSN

2.1 什麼是RTL,什麼是INSN

  RTL叫做暫存器轉移語言(Register Transfering Language)。說是暫存器,其實也包含記憶體操作。RTL被設計成一種函式式語言,由表示式和物件構成。其中物件指的是暫存器、記憶體和值(常數或者表示式的值),表示式就是對物件和子表示式的操作。這些在gcc internal裡面都有介紹。

  RTL物件和操作組成RTL表示式,子表示式加上操作組成複合RTL表示式。當一個RTL表示式表示一條中間語言指令時,這個RTL表示式叫做INSN。RTL表示式(RTL Expression)在gcc程式碼中縮寫為RTX,程式碼中的rtx型別就是指向RTL表示式的指標。所以insn就是rtx,但是rtx不一定是insn。

2.2 INSN的生成

  RTL是由gimple生成的,從gimple到RTL的轉換叫做“expand”。在整個優化的pass鏈中,這一步由pass_expand完成。該pass實現在gcc/cfgexpand.c中。它的execute函式gimple_expand_cfg很長,但是核心工作是對每個basic block進行轉換:

[cpp] view plaincopy
  1. FOR_BB_BETWEEN (bb, init_block->next_bb, EXIT_BLOCK_PTR, next_bb)  
  2.   bb = expand_gimple_basic_block (bb);  
expand_gimple_basic_block會呼叫expan_gimple_stmt來展開每一個gimple語句,並將展開後的rtx連線在一起。首先就有一個問題:insn是怎麼生成的?

  此外,每個expand_xxx函式只負責一部分工作,有些函式有rtx型別的返回值,有些函式沒有返回值。那些有返回值的函式通常也不會有變數來儲存它們返回的insn。那麼就有另外一個問題:那些展開的insn到哪裡去了?

  為了弄清楚這兩個問題,首先要找到生成insn的地方。這是一項工程浩大的體力活,不妨從某個點來研究這個問題,比如就從函式呼叫的語句來入手吧。我們可以從expand_gimple_basic_block開始順藤摸瓜,來看看一個GIMPLE_CALL是如何翻譯成insn的。

  首先,expand_gimple_basic_block裡有一個對basic block裡的gimple statement的遍歷迴圈,在這個迴圈裡面,首先判斷了一些特殊的情況,比如debug之類的,忽略之。直到迴圈最後一部分才進入正題:

[cpp] view plaincopy
  1.  if (is_gimple_call (stmt) && gimple_call_tail_p (stmt)) // 尾呼叫,特殊情況,忽略之  
  2.    {  
  3.      bool can_fallthru;  
  4.      new_bb = expand_gimple_tailcall (bb, stmt, &can_fallthru);  
  5.      if (new_bb)  
  6. {  
  7.   if (can_fallthru)  
  8.     bb = new_bb;  
  9.   else  
  10.     return new_bb;  
  11. }  
  12.    }  
  13.  else  
  14.    {  
  15.      def_operand_p def_p;  
  16.      def_p = SINGLE_SSA_DEF_OPERAND (stmt, SSA_OP_DEF);  
  17.   
  18.      if (def_p != NULL)  
  19. {  
  20.   /* Ignore this stmt if it is in the list of 
  21.      replaceable expressions.  */  
  22.   if (SA.values  
  23.       && bitmap_bit_p (SA.values,  
  24.                SSA_NAME_VERSION (DEF_FROM_PTR (def_p))))  
  25.     continue;  
  26. }  
  27.      last = expand_gimple_stmt (stmt); //<strong> </strong>這是真正幹活的地方  
  28.      maybe_dump_rtl_for_gimple_stmt (stmt, last);  
  29.    }  

  進入到expand_gimple_stmt裡面,這個函式不長,一眼可以看出來,核心是expand_gimple_stmt_1 (stmt);,這個函式分情況展開了stmt。其中GIMPLE_CALL對應的是expand_call_stmt。這個函式也不長,關鍵在最後。

[cpp] view plaincopy
  1. if (lhs)  
  2.   expand_assignment (lhs, exp, false); // lhs = func(args)  
  3. else  
  4.   expand_expr_real_1 (exp, const0_rtx, VOIDmode, EXPAND_NORMAL, NULL); // func(args)  

  gimple call語句形如 lhs = func ( args ); 。其中,lhs是可以沒有的。所以如果存在lhs的話,就按賦值語句展開。否則的話就按表示式展開。賦值語句的右邊也是表示式,因此按賦值語句展開最終也會將“func(args)”部分按表示式展開。

  expand_gimple_expr_1函式很長,因為要處理的表示式型別比較多。其中我們關注的是case CALL_EXPR:分支:

[cpp] view plaincopy
  1.    case CALL_EXPR:  
  2.      /* All valid uses of __builtin_va_arg_pack () are removed during 
  3.  inlining.  */  
  4.      if (CALL_EXPR_VA_ARG_PACK (exp))  
  5. error ("%Kinvalid use of %<__builtin_va_arg_pack ()%>", exp);  
  6.      {  
  7. tree fndecl = get_callee_fndecl (exp), attr;  
  8.   
  9. if (fndecl  
  10.     && (attr = lookup_attribute ("error",  
  11.                  DECL_ATTRIBUTES (fndecl))) != NULL)  
  12.   error ("%Kcall to %qs declared with attribute error: %s",  
  13.      exp, identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 1)),  
  14.      TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr))));  
  15. if (fndecl  
  16.     && (attr = lookup_attribute ("warning",  
  17.                  DECL_ATTRIBUTES (fndecl))) != NULL)  
  18.   warning_at (tree_nonartificial_location (exp),  
  19.           0, "%Kcall to %qs declared with attribute warning: %s",  
  20.           exp, identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 1)),  
  21.           TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr))));  
  22.   
  23. /* Check for a built-in function.  */  
  24. if (fndecl && DECL_BUILT_IN (fndecl))  
  25.   {  
  26.     gcc_assert (DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_FRONTEND);  
  27.     return expand_builtin (exp, target, subtarget, tmode, ignore); // 內建函式  
  28.   }  
  29.      }  
  30.      return expand_call (exp, target, ignore); // 普通函式  

  內建函式有內建函式的展開方法,這個以後有機會再講。這裡還是分析一下普通函式。前面的那個if 是用來檢查的,展開是由expand_call函式來完成。這個函式相當長,因為函式的引數、堆疊等等事務很繁瑣。但是至少可以確定的是,一句普通的函式呼叫絕對不是一個簡單的insn能實現的,它應該對應了一串insn,而且至少包括壓棧、呼叫、退棧這三部分。那麼這一串insn在哪裡?

  為了弄清楚這一串insn在程式碼中的哪個地方,就必須提到start_sequence ()、get_insns()、end_sequence()這三個沒有引數的函式。第一個函式開啟了一個新的insn sequence,第二個函式獲取這個sequence的第一個insn,因為sequence是雙鏈表,所以由第一個insn就可以訪問到後面的所有insn。最後一個函式關閉這個sequence,之後就不能再通過emit_xxx往這個sequence裡面插入insn了。原因現在還說不清楚,因為這個跟第二個問題相關,就是insn去哪裡了?

  那麼insn到哪裡去了?在expand_call這個函式最後就有答案:

[cpp] view plaincopy
  1. /* If tail call production succeeded, we need to remove REG_EQUIV notes on 
  2.    arguments too, as argument area is now clobbered by the call.  */  
  3. if (tail_call_insns)  
  4.   {  
  5.     emit_insn (tail_call_insns); // 尾呼叫的rtx  
  6.     crtl->tail_call_emit = true;  
  7.   }  
  8. else  
  9.   emit_insn (normal_call_insns); // 正常呼叫的rtx  
  10.   
  11. currently_expanding_call--;  
  12.   
  13. if (stack_usage_map_buf)  
  14.   free (stack_usage_map_buf);  
  15.   
  16. return target;  

  所謂尾呼叫就相當於 return tail_call(...);。這個是有專門優化的。但不管怎麼優化,最後的insn被髮射(emit)了:

[cpp] view plaincopy
  1. rtx  
  2. emit_insn (rtx x)  
  3. {  
  4.   rtx last = last_insn;  
  5.   rtx insn;  
  6.   
  7.   if (x == NULL_RTX)  
  8.     return last;  
  9.   
  10.   switch (GET_CODE (x))  
  11.     {  
  12.     // 忽略那些特殊的case  
  13.     default:  
  14.       last = make_insn_raw (x);  
  15.       add_insn (last); // 這裡  
  16.       break;  
  17.     }  
  18.   
  19.   return last;  
  20. }  
  21. void  
  22. add_insn (rtx insn) // 一個標準的雙鏈表插入演算法  
  23. {  
  24.   PREV_INSN (insn) = last_insn;  
  25.   NEXT_INSN (insn) = 0;  
  26.   
  27.   if (NULL != last_insn)  
  28.     NEXT_INSN (last_insn) = insn;  
  29.   
  30. 相關推薦

    GCC原始碼分析——指令生成

    原文連結:http://blog.csdn.net/sonicling/article/details/8246231 一、前言   又有好久沒寫了,的確很忙。前篇介紹了GCC的pass格局,它是GCC中間語言部分的核心架構,也是貫穿整個編譯流程的核心。在完成優化處理之

    轉載:GCC原始碼分析——指令生成

    一、前言   又有好久沒寫了,的確很忙。前篇介紹了GCC的pass格局,它是GCC中間語言部分的核心架構,也是貫穿整個編譯流程的核心。在完成優化處理之後,GCC必須做的最後一步就是生成最後的編譯結果,通常情況下就是彙編檔案(文字或者二進位制並不重要)。   前面也講到了,

    Flume NG原始碼分析使用ThriftSource通過RPC方式收集日誌

    上一篇說了利用ExecSource從本地日誌檔案非同步的收集日誌,這篇說說採用RPC方式同步收集日誌的方式。筆者對Thrift比較熟悉,所以用ThriftSource來介紹RPC的日誌收集方式。 整體的結構圖如下: 1. ThriftSource包含了一個Thrift Server,以及一個

    OpenCV學習筆記31KAZE 演算法原理與原始碼分析KAZE的原始碼優化及與SIFT的比較

      KAZE系列筆記: 1.  OpenCV學習筆記(27)KAZE 演算法原理與原始碼分析(一)非線性擴散濾波 2.  OpenCV學習筆記(28)KAZE 演算法原理與原始碼分析(二)非線性尺度空間構建 3.  Op

    GCC原始碼分析——中間語言

    原文連結:http://blog.csdn.net/sonicling/article/details/7915301 一、前言   很忙,很久沒更新部落格了,繼續沒寫完的gcc分析,爭取在傳說將要用C++重寫的gcc 5出來之前初略分析完。 二、符號表(GENERI

    GCC原始碼分析——優化

    原文連結:http://blog.csdn.net/sonicling/article/details/7916931 一、前言 本篇只介紹一下框架,就不具體介紹每個步驟了。 二、Pass框架 上一篇已經講了gcc的中間語言的表現形式。gcc 對中間語言

    GCC原始碼分析——介紹與安裝

    原文連結:http://blog.csdn.net/sonicling/article/details/6702031     上半年一直在做有關GCC和LD的專案,到現在還沒做完。最近幾天程式設計的那臺電腦壞了,所以趁此間隙寫一點相關的分析和

    GCC原始碼分析——前端

    原文連結:http://blog.csdn.net/sonicling/article/details/6706152   從這一篇開始,我們將從原始碼的角度來分析GCC如何完成對C語言原始檔的處理。GCC的內部構架在GCC Internals(搜“gccint.pdf”,或者見[

    YOLOv2原始碼分析

    文章全部YOLOv2原始碼分析 0x01 make_convolutional_layer 終於又回到了make_convolutional_layer這個函式 //make_convolutional_layer

    AFNetWorking(3.0)原始碼分析——AFHTTPRequestSerializer & AFHTTPResponseSerializer

    在前面的幾篇部落格中,我們分析了AFURLSessionMangerd以及它的子類AFHTTPSessionManager。我們對AF的主要兩個類,有了一個比較全面的瞭解。 對於AFHTTPSessionManager,當其在要傳送請求時,會呼叫AFHTTPRequestSerial

    vlc原始碼分析 流媒體的音視訊同步

    轉載地址:https://www.cnblogs.com/jiayayao/p/6890882.html vlc播放流媒體時實現音視訊同步,簡單來說就是傳送方傳送的RTP包帶有時間戳,接收方根據此時間戳不斷校正本地時鐘,播放音視訊時根據本地時鐘進行同步播放。首先了解兩個概念

    libevent原始碼分析

    libevent-1.4/sample/signal-test.c event_add(&signal_int, NULL); 將 struct event signal_int新增到struct event_base* base,即註冊號監聽事件以及回

    mochiweb原始碼分析

    1.接著前面講解的生成Req物件,看new_request/4的函式,這裡主要是儲存請求行資訊和請求頭部。 {packet, raw}是不設定訊息打包規則,如果{packet, 1|2|4},則表示每一個包都會帶上一個N(1,2或4)位元組長的長度計數。

    RxJava2.0中flatMap操作符用法和原始碼分析

    flatMap基本使用 flatMap是變換操作符,使用一個指定的函式對原始Observable發射的每一項資料執行變換操作,這個函式返回一個本身也發射資料的Observable,然後flatMap合併這些Observable發射的資料,最後將合併後的結果當作

    spring4.2.9 java專案環境下ioc原始碼分析——refresh之obtainFreshBeanFactory方法@3預設標籤import,alias解析

    接上篇文章,到了具體解析的時候了,具體的分為兩種情況,一種是預設名稱空間的標籤<bean>;另一種是自定義名稱空間的標籤比如<context:xxx>,<tx:xxx>等。先看下預設的名稱空間的標籤解析。protected void par

    crawler4j原始碼分析Robots協議

           本節來看看crawler4j是如何支援robots協議的。對robots協議的支援主要目的就是遵守禮貌爬取,即:按照伺服器制定的規則來爬取,只抓取允許抓取的,不讓抓的不抓。 在crawler4j中對robots的支援包括如下幾個類:RobotstxtConfi

    ZMQ原始碼分析 --TCP通訊

    zmq支援tcp,inpro,ipc,pgm,epgm,tipc等通訊方式。只要在address中指定地址格式即可透明支援對應的通訊方式。這裡我們以最常用的tcp為例分析zmq資料在網路間傳輸的流程,這部分是zmq中最複雜也是最重的部分,在分析流程之前,先看一下

    python3.6 原始碼分析:類的建立

    友情提示:類的建立過程非常複雜, 請自備小本本 位元組碼分析 先來個最簡單的類: class A: pass 編譯一下: 0 LOAD_BUILD_CLASS 2 LOAD_C

    Java多執行緒之ThreadPoolExecutor實現原理和原始碼分析

    章節概覽、 1、概述 執行緒池的顧名思義,就是執行緒的一個集合。需要用到執行緒,從集合裡面取出即可。這樣設計主要的作用是優化執行緒的建立和銷燬而造成的資源浪費的情況。Java中的執行緒池的實現主要是JUC下面的ThreadPoolExecutor類完成的。下面

    Libevent原始碼分析--- evbuffer的基本操作

    之前幾節分析了libevent底層的結構和執行機制,接下來的幾節將會分析Bufferevents,Bufferevents在event的基礎上加入了資料快取邏輯,使得事件和資料結合在一起。libevent的bufferevent有六種型別,分別是:buffere