筆者來聊聊編譯器的用法
arm編譯器學習
首先來了解一下編譯器,其通常分為三個部分:前端+優化器+后端。
前端:詞法、語法和語義分析,將源代碼轉化為抽象語法樹,生成中間代碼
優化器:對得到的中間代碼進行優化,使得代碼更加高效,
后端:將優化的代碼轉化為針對各自平臺的機器代碼。
再通俗地說編譯器的工作就是:源代碼->預處理->編譯->目標代碼->鏈接->可執行程序。
再來簡單看看一些編譯器的歷史,GCC、LLVM以及Clang等,以及文章介紹的armcc 以及armclang。
GCC (GNU Compiler Collection)是GNU開發的編譯器,許可證為GPL的自由軟件;
GCC 原來只能處理C,現在可以處理C++、Pascal、Object-C、Java等。
蘋果公司之前一直使用GCC作為編譯器,但是GCC對Objective-C支持一直不怎么好,好多新特性沒有增加,所以蘋果公司開始尋求編譯器的替代品。
這個時候LLVM就出現了,是Chris Lattner在碩士和博士時提出和形成的編譯器,不過其是采用GCC的前端進行語義分析,然后LLVM做優化和生成目標代碼,可以叫做LLVM-GCC。
后來蘋果公司直接計劃繞開GCC,于是招募了Chris Lattner 博士開發編譯器,Clang就這樣誕生了,其基于LLVM開發的C/C++/Obj-C編譯器,實際上其是一個編譯器前端,來取代GCC或者超越GCC
armcc 是arm 公司開發的一款編譯器,集成在KEIL以及ARM DS IDE里面,于5.06版本后停滯(AC5),不繼續維護,其前端基于 Edison Design Group 。
armclang 集成于armcc,基于新的架構 clang 和LLVM,作為arm 的第六代編譯器,AC6,成為今后主推的編譯器。
armcc 編譯器
arm 公司 開發的一款編譯器,在2005年收購 KEIL 公司后,這塊編譯器就集成在KEIL IDE里面,以及自家開發的ARM DS5,編譯器以及IDE相關的文檔可以去ARM 公司的官網下載。
下載的文檔主要分幾個部分:armcc 編譯器、armasm 匯編器、armlink 鏈接器、armar 打包以及fromelf bin文件。
1、armcc
armcc 編譯器 主要是編譯.c/.cpp源文件文件,生成目標文件,通過各種編譯選項 command-line來支持各種特性。接著來羅列幾個常見的編譯選項。
一般的arm cc的編譯器的編譯器的語法如下:
?
armcc?[options]?[source]? 舉例如下: armcc?-I?../common/?-I?../driver??-g?--apcs=interwork?--cpu=Cortex-R5?-c?../common/led.c?-o?../out/led.o 123
?
-c/-C/-o/-D-c 代表 只是編譯,不進入鏈接步驟,-C 保留預處理的輸出,然后-E 可以指定預處理輸出到某個指定文件。
?
armcc?-c?-C?-E??-I?../common/?-I?../driver?-g?--apcs=interwork?--cpu=Cortex-R5?../common/led.c?-o?../out/led.i 這樣之后,可以看到預處理的結果,比如宏替換后的結果,方便分析問題。 12
?
-o 指定輸出的文件名稱
-D 定義宏名稱,例如:-DLOG -DUART=1 -U 移除已經定義的宏名稱
?
#define?LOG #define?UART?1 在編譯器命令行指定上面的宏,相當于在程序里面定義上述代碼的定義 1234
?
-I:指定include的目錄 ,如果路徑沒指定,編譯階段就會報錯,找不到相關的文件,相比大家都見過這個錯誤吧!
–c99 --c90 指的的是C語言的語法版本,
–cpu=name 比如 --cpu=Cortex-R5
-M/–md 這兩個是用來為每個源文件產生編譯依賴,–md 生成.d文件,表示這個目標文件所依賴的頭文件。這個在增量編譯非常有用,再找到依賴關系后,更新依賴,則可以只編譯修改的文件以及依賴的文件。
?
armcc??-c?-M??-I?..SYSTEMsys??-I?...??sys.c?--no_depend_single_line?--md?? 1
![0499a9e0-6b8c-11ed-8abf-dac502259ad0.png](https://file1.elecfans.com//web2/M00/97/FB/wKgZomTnR7aAZRlYAAGxRSBNHQc213.png)
?
–diag_error/–diag_suppress/–diag_warning 對編譯的警告以及錯誤進行處理,比如屏蔽某個編譯警告/錯誤
?
--diag_error=warning??????????????????????將err的編譯消息視為warning, --diag_suppress=3017,1256,1148????????????將編譯消息?編碼為?3017,1256,1148的診斷消息屏蔽 --diag_warning=1234,5678??????????????????屏蔽編碼為?1234,5678的warning的診斷消息?????? --diag_warning=error??????????????????????將warning視為error 1234
?
例如下面的20、223 這種編碼序號。
在這里插入圖片描述
–feedback=filename 編譯反饋,主要是用來去除沒有用到的代碼 (數據以及code),需要與鏈接的選項一起使用,通常需要編譯兩次。
?
--feedback=unused_section.txt???編譯器階段把沒用到的代碼和code單獨放在一個section,方便鏈接階段去除,鏈接階段,生成不用的section區 --feedback=image_none???????????忽略鏈接階段的鏈接腳本,忽略代碼布局,則不會生成axf文件 --remove????????????????????????去除不用的section --keep?memory_alyout.o(rw)????可以設置memory_out.o中的rw段不刪除????????????????????????? 通過feedback,空間從950k?->?800k?(雙core的bin?所需空間) 12345
?
–inline/–forceinline
前者會對函數是否內斂進行考慮,后者強制將所有函數進行內斂,要對單個函數進行內斂,可以考慮對函數進行修飾,__forceinline。
需要注意的是,并不是所有的函數都可以內聯,比如遞歸函數。
–littleend/–bigend 數據大小端設置,
-O0/O1/O2/O3/Otime/Ospace 編譯優化選項
-O0最小優化。關閉大多數優化。啟用調試時,此選項提供最佳調試視圖,因為生成代碼的結構直接對應于源代碼。所有干擾調試視圖的優化都被禁用。
可以在任何可到達的點設置斷點,包括死代碼(程序執行不到的地方 或者沒有受調用的地方)。
變量的值在其范圍內的任何地方都可用,但它所在的位置除外未初始化。
Backtrace 提供了讀取源代碼時預期的函數調用棧關系。
雖然 -O0 生成的調試視圖與源代碼最接近,但用戶可能更喜歡 -O1 生成的調試視圖,因為這提高了代碼的質量在不改變基本結構的情況下。
死代碼包括對程序結果沒有影響的可達代碼,例如對從未使用過的局部變量的賦值。無法訪問的代碼是專門的代碼無法通過任何控制流路徑訪問,例如緊跟在返回之后的代碼陳述。
-O1受限優化。編譯器只執行可以描述為調試信息的優化。刪除未使用的內聯函數和未使用的靜態函數。關掉嚴重降低調試視圖的優化。如果與 –debug 一起使用,此選項會給出總體上令人滿意的調試視圖且具有良好的代碼密度。調試視圖與 –O0 的區別在于:
不能在死代碼上設置斷點。
變量的值在初始化后可能在其范圍內不可用。例如,如果他們分配的位置已被重復使用。
沒有影響的函數可能會被亂序調用,或者如果結果是不需要的。
Backtrace 可能不準確,因為在棧的方面處理有變化,存在調用優化。
優化級別 –O1 在源代碼和對象之間產生良好的對應關系代碼,特別是當源代碼不包含死代碼時。
生成的代碼可以是明顯小于 –O0 處的代碼,這可以簡化目標代碼的分析。
-O2高度優化。如果與 --debug 一起使用,調試視圖可能不太令人滿意,因為目標代碼到源代碼的映射并不總是清晰的。編譯器可能會執行調試信息無法描述的優化。這是默認的優化級別。調試視圖與 –O1 的區別在于:
源代碼到目標代碼的映射可能是多對一的,因為可能多個源代碼位置映射到目標文件的一個點,更激進的指令優化。
允許指令調度跨越序列點。這可能導致變量在特定點的報告值與期望的值不匹配。
編譯器自動內聯函數
-O3最大優化。啟用調試后,此選項通常會提供較差的調試視圖。ARM 建議在較低的優化級別進行調試。如果同時使用 -O3 和 -Otime,編譯器會執行更積極的額外優化,例如:
高級標量優化,包括循環展開。這可以給顯著以較小的代碼大小成本獲得性能優勢,但存在構建時間較長的風險。
更積極的內聯和自動內聯。
這些優化有效地重寫了輸入源代碼,導致目標代碼與源代碼的最低對應和最差的調試視圖。--loop_optimization_level=option ,控制在 –O3 –Otime 執行的循環優化效果。循環優化的數量越高,源代碼和目標代碼之間的對應關系就越差。
使用 --vectorize 選項還降低了源代碼和目標代碼之間的對應關系。有關在源代碼上執行的高級轉換的更多信息,請訪問–O3 –Otime 使用 --remarks 命令行選項。
因為優化會影響目標代碼到源代碼的映射,所以使用 -Ospace 和 -Otime 選擇優化級別通常會影響調試視圖。
如果需要簡單的調試視圖,選項 -O0 是最好的選擇。選擇 -O0 通常會將 ELF 映像的大小增加 7% 到 15%。要減小調試表的大小,請使用–remove_unneeded_entities 選項
–split_sections為每個源文件的函數創建一個section,方便在鏈接的時候去掉.o文件 中的不用的函數。–attribute((section(…))) 可以修飾data 和 function,將其放到指定的section,而不是放到默認的section
–thumb將該.c文件編譯成 thumb指令,
?
#pragma??arm?????????編譯成arm指令 #pragma??thumb???????編譯成thumb指令 #pragam??push????????保存#pragma?狀態 #pragma??pop?????????彈出狀態?與上面的可以一起使用 #pragma? pack(n)???設置 n字節對齊,對于結構體來說。 12345
?
–use_frame_pointer這個設置棧頂指針,每次進入函數后,會首先將棧頂壓入棧,之后再做其他的寄存器壓棧,這樣的好處是backtrace的調用關系很容易找出來。詳見ARM開發中幾個常見的寄存器詳解
-apcs=interwork 支持內部thumb與arm 指令相互切換,比如BLX,這個支持thumb指令的地方用處較多,
2、armasm
嵌入式匯編
函數形參列表可以使用變量,但是函數體必須要用寄存器,函數體都是匯編語言實現
需要匯編語言處理返回指令
?
__asm?return-type?function-name(parameter-list) { //?ARM/Thumb?assembly?code instruction{;comment?is?optional} ... instruction } /*示例1*/ __asm?int?f(int?i) { ?ADD?r0,?r0,?#1? } /*示例2*/ #include?__asm?void?my_strcpy(const?char?*src,?char?*dst) { loop ?LDRB?r2,?[r0],?#1 ?STRB?r2,?[r1],?#1 ?CMP?r2,?#0 ?BNE?loop ?BX?lr } int?main(void) { ?const?char?*a?=?"Hello?world!"; ?char?b[20]; ?my_strcpy?(a,?b); ?printf("Original?string:?'%s' ",?a); ?printf("Copied?string:?'%s' ",?b); ?return?0; }
?
內聯匯編
同一行如果有多行指令,必須要有封號(;)
如果一個指令超出一行,需要增加反斜杠()
在多行格式中,允許在內聯匯編語言塊中的任何位置使用C和C++注釋。但是注釋不能嵌入到多條指令的行中。
在匯編語言中,逗號(,)用作分隔符,所以C表達式的逗號運算符必須用括號括起來來和它們進行區分
標簽必須后跟冒號,:,如C和C++標簽
asm語句必須位于C++函數內部。asm語句可以在任何需要C++語句的地方使用
內聯程序集代碼中的寄存器名被視為C或C++變量。它們不一定與同名的物理寄存器有關。如果寄存器未聲明為C或C++變量,編譯器將生成警告
不得在內聯程序集代碼中保存和還原寄存器,編譯器會執行此操作。此外,內聯匯編程序不提供對物理寄存器的直接訪問。然而,可以通過變量間接訪問寄存器
pc/lr/sp:__current_pc,__current_sp, and __return_address 來read
內聯匯編中不要修改處理器模式或者協處理器的狀態
?
int?f(int?x) { ?__asm ?{ ??STMFD?sp!,?{r0}?//?save?r0?-?illegal:?read?before?write ??ADD?r0,?x,?1 ??EOR?x,?r0,?x ??LDMFD?sp!,?{r0}?//?restore?r0?-?not?needed. ?} ?return?x; } The?function?must?be?written?as: int?f(int?x) { ?int?r0; ?__asm ?{ ??ADD?r0,?x,?1 ??EOR?x,?r0,?x ?} ?return?x; } int?foo(int?x,?int?y) { __asm { ?SUBS?x,x,y ?BEQ?end } return?1; end: ?return?0; }
?
由于篇幅原因,后續再補充armclang的知識。
審核編輯:湯梓紅
評論