筆者來聊聊編譯器的用法
arm編譯器學(xué)習(xí)
首先來了解一下編譯器,其通常分為三個(gè)部分:前端+優(yōu)化器+后端。
前端:詞法、語法和語義分析,將源代碼轉(zhuǎn)化為抽象語法樹,生成中間代碼
優(yōu)化器:對(duì)得到的中間代碼進(jìn)行優(yōu)化,使得代碼更加高效,
后端:將優(yōu)化的代碼轉(zhuǎn)化為針對(duì)各自平臺(tái)的機(jī)器代碼。
再通俗地說編譯器的工作就是:源代碼->預(yù)處理->編譯->目標(biāo)代碼->鏈接->可執(zhí)行程序。
再來簡單看看一些編譯器的歷史,GCC、LLVM以及Clang等,以及文章介紹的armcc 以及armclang。
GCC (GNU Compiler Collection)是GNU開發(fā)的編譯器,許可證為GPL的自由軟件;
GCC 原來只能處理C,現(xiàn)在可以處理C++、Pascal、Object-C、Java等。
蘋果公司之前一直使用GCC作為編譯器,但是GCC對(duì)Objective-C支持一直不怎么好,好多新特性沒有增加,所以蘋果公司開始尋求編譯器的替代品。
這個(gè)時(shí)候LLVM就出現(xiàn)了,是Chris Lattner在碩士和博士時(shí)提出和形成的編譯器,不過其是采用GCC的前端進(jìn)行語義分析,然后LLVM做優(yōu)化和生成目標(biāo)代碼,可以叫做LLVM-GCC。
后來蘋果公司直接計(jì)劃繞開GCC,于是招募了Chris Lattner 博士開發(fā)編譯器,Clang就這樣誕生了,其基于LLVM開發(fā)的C/C++/Obj-C編譯器,實(shí)際上其是一個(gè)編譯器前端,來取代GCC或者超越GCC
armcc 是arm 公司開發(fā)的一款編譯器,集成在KEIL以及ARM DS IDE里面,于5.06版本后停滯(AC5),不繼續(xù)維護(hù),其前端基于 Edison Design Group 。
armclang 集成于armcc,基于新的架構(gòu) clang 和LLVM,作為arm 的第六代編譯器,AC6,成為今后主推的編譯器。
armcc 編譯器
arm 公司 開發(fā)的一款編譯器,在2005年收購 KEIL 公司后,這塊編譯器就集成在KEIL IDE里面,以及自家開發(fā)的ARM DS5,編譯器以及IDE相關(guān)的文檔可以去ARM 公司的官網(wǎng)下載。
下載的文檔主要分幾個(gè)部分:armcc 編譯器、armasm 匯編器、armlink 鏈接器、armar 打包以及fromelf bin文件。
1、armcc
armcc 編譯器 主要是編譯.c/.cpp源文件文件,生成目標(biāo)文件,通過各種編譯選項(xiàng) command-line來支持各種特性。接著來羅列幾個(gè)常見的編譯選項(xiàng)。
一般的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 代表 只是編譯,不進(jìn)入鏈接步驟,-C 保留預(yù)處理的輸出,然后-E 可以指定預(yù)處理輸出到某個(gè)指定文件。
?
armcc?-c?-C?-E??-I?../common/?-I?../driver?-g?--apcs=interwork?--cpu=Cortex-R5?../common/led.c?-o?../out/led.i 這樣之后,可以看到預(yù)處理的結(jié)果,比如宏替換后的結(jié)果,方便分析問題。 12
?
-o 指定輸出的文件名稱
-D 定義宏名稱,例如:-DLOG -DUART=1 -U 移除已經(jīng)定義的宏名稱
?
#define?LOG #define?UART?1 在編譯器命令行指定上面的宏,相當(dāng)于在程序里面定義上述代碼的定義 1234
?
-I:指定include的目錄 ,如果路徑?jīng)]指定,編譯階段就會(huì)報(bào)錯(cuò),找不到相關(guān)的文件,相比大家都見過這個(gè)錯(cuò)誤吧!
–c99 --c90 指的的是C語言的語法版本,
–cpu=name 比如 --cpu=Cortex-R5
-M/–md 這兩個(gè)是用來為每個(gè)源文件產(chǎn)生編譯依賴,–md 生成.d文件,表示這個(gè)目標(biāo)文件所依賴的頭文件。這個(gè)在增量編譯非常有用,再找到依賴關(guān)系后,更新依賴,則可以只編譯修改的文件以及依賴的文件。
?
armcc??-c?-M??-I?..SYSTEMsys??-I?...??sys.c?--no_depend_single_line?--md?? 1在這里插入圖片描述
?
–diag_error/–diag_suppress/–diag_warning 對(duì)編譯的警告以及錯(cuò)誤進(jìn)行處理,比如屏蔽某個(gè)編譯警告/錯(cuò)誤
?
--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 這種編碼序號(hào)。
在這里插入圖片描述
–feedback=filename 編譯反饋,主要是用來去除沒有用到的代碼 (數(shù)據(jù)以及code),需要與鏈接的選項(xiàng)一起使用,通常需要編譯兩次。
?
--feedback=unused_section.txt???編譯器階段把沒用到的代碼和code單獨(dú)放在一個(gè)section,方便鏈接階段去除,鏈接階段,生成不用的section區(qū) --feedback=image_none???????????忽略鏈接階段的鏈接腳本,忽略代碼布局,則不會(huì)生成axf文件 --remove????????????????????????去除不用的section --keep?memory_alyout.o(rw)????可以設(shè)置memory_out.o中的rw段不刪除????????????????????????? 通過feedback,空間從950k?->?800k?(雙core的bin?所需空間) 12345
?
–inline/–forceinline
前者會(huì)對(duì)函數(shù)是否內(nèi)斂進(jìn)行考慮,后者強(qiáng)制將所有函數(shù)進(jìn)行內(nèi)斂,要對(duì)單個(gè)函數(shù)進(jìn)行內(nèi)斂,可以考慮對(duì)函數(shù)進(jìn)行修飾,__forceinline。
需要注意的是,并不是所有的函數(shù)都可以內(nèi)聯(lián),比如遞歸函數(shù)。
–littleend/–bigend 數(shù)據(jù)大小端設(shè)置,
-O0/O1/O2/O3/Otime/Ospace 編譯優(yōu)化選項(xiàng)
-O0最小優(yōu)化。關(guān)閉大多數(shù)優(yōu)化。啟用調(diào)試時(shí),此選項(xiàng)提供最佳調(diào)試視圖,因?yàn)樯纱a的結(jié)構(gòu)直接對(duì)應(yīng)于源代碼。所有干擾調(diào)試視圖的優(yōu)化都被禁用。
可以在任何可到達(dá)的點(diǎn)設(shè)置斷點(diǎn),包括死代碼(程序執(zhí)行不到的地方 或者沒有受調(diào)用的地方)。
變量的值在其范圍內(nèi)的任何地方都可用,但它所在的位置除外未初始化。
Backtrace 提供了讀取源代碼時(shí)預(yù)期的函數(shù)調(diào)用棧關(guān)系。
雖然 -O0 生成的調(diào)試視圖與源代碼最接近,但用戶可能更喜歡 -O1 生成的調(diào)試視圖,因?yàn)檫@提高了代碼的質(zhì)量在不改變基本結(jié)構(gòu)的情況下。
死代碼包括對(duì)程序結(jié)果沒有影響的可達(dá)代碼,例如對(duì)從未使用過的局部變量的賦值。無法訪問的代碼是專門的代碼無法通過任何控制流路徑訪問,例如緊跟在返回之后的代碼陳述。
-O1受限優(yōu)化。編譯器只執(zhí)行可以描述為調(diào)試信息的優(yōu)化。刪除未使用的內(nèi)聯(lián)函數(shù)和未使用的靜態(tài)函數(shù)。關(guān)掉嚴(yán)重降低調(diào)試視圖的優(yōu)化。如果與 –debug 一起使用,此選項(xiàng)會(huì)給出總體上令人滿意的調(diào)試視圖且具有良好的代碼密度。調(diào)試視圖與 –O0 的區(qū)別在于:
不能在死代碼上設(shè)置斷點(diǎn)。
變量的值在初始化后可能在其范圍內(nèi)不可用。例如,如果他們分配的位置已被重復(fù)使用。
沒有影響的函數(shù)可能會(huì)被亂序調(diào)用,或者如果結(jié)果是不需要的。
Backtrace 可能不準(zhǔn)確,因?yàn)樵跅5姆矫嫣幚碛凶兓?,存在調(diào)用優(yōu)化。
優(yōu)化級(jí)別 –O1 在源代碼和對(duì)象之間產(chǎn)生良好的對(duì)應(yīng)關(guān)系代碼,特別是當(dāng)源代碼不包含死代碼時(shí)。
生成的代碼可以是明顯小于 –O0 處的代碼,這可以簡化目標(biāo)代碼的分析。
-O2高度優(yōu)化。如果與 --debug 一起使用,調(diào)試視圖可能不太令人滿意,因?yàn)槟繕?biāo)代碼到源代碼的映射并不總是清晰的。編譯器可能會(huì)執(zhí)行調(diào)試信息無法描述的優(yōu)化。這是默認(rèn)的優(yōu)化級(jí)別。調(diào)試視圖與 –O1 的區(qū)別在于:
源代碼到目標(biāo)代碼的映射可能是多對(duì)一的,因?yàn)榭赡芏鄠€(gè)源代碼位置映射到目標(biāo)文件的一個(gè)點(diǎn),更激進(jìn)的指令優(yōu)化。
允許指令調(diào)度跨越序列點(diǎn)。這可能導(dǎo)致變量在特定點(diǎn)的報(bào)告值與期望的值不匹配。
編譯器自動(dòng)內(nèi)聯(lián)函數(shù)
-O3最大優(yōu)化。啟用調(diào)試后,此選項(xiàng)通常會(huì)提供較差的調(diào)試視圖。ARM 建議在較低的優(yōu)化級(jí)別進(jìn)行調(diào)試。如果同時(shí)使用 -O3 和 -Otime,編譯器會(huì)執(zhí)行更積極的額外優(yōu)化,例如:
高級(jí)標(biāo)量優(yōu)化,包括循環(huán)展開。這可以給顯著以較小的代碼大小成本獲得性能優(yōu)勢,但存在構(gòu)建時(shí)間較長的風(fēng)險(xiǎn)。
更積極的內(nèi)聯(lián)和自動(dòng)內(nèi)聯(lián)。
這些優(yōu)化有效地重寫了輸入源代碼,導(dǎo)致目標(biāo)代碼與源代碼的最低對(duì)應(yīng)和最差的調(diào)試視圖。--loop_optimization_level=option ,控制在 –O3 –Otime 執(zhí)行的循環(huán)優(yōu)化效果。循環(huán)優(yōu)化的數(shù)量越高,源代碼和目標(biāo)代碼之間的對(duì)應(yīng)關(guān)系就越差。
使用 --vectorize 選項(xiàng)還降低了源代碼和目標(biāo)代碼之間的對(duì)應(yīng)關(guān)系。有關(guān)在源代碼上執(zhí)行的高級(jí)轉(zhuǎn)換的更多信息,請(qǐng)?jiān)L問–O3 –Otime 使用 --remarks 命令行選項(xiàng)。
因?yàn)閮?yōu)化會(huì)影響目標(biāo)代碼到源代碼的映射,所以使用 -Ospace 和 -Otime 選擇優(yōu)化級(jí)別通常會(huì)影響調(diào)試視圖。
如果需要簡單的調(diào)試視圖,選項(xiàng) -O0 是最好的選擇。選擇 -O0 通常會(huì)將 ELF 映像的大小增加 7% 到 15%。要減小調(diào)試表的大小,請(qǐng)使用–remove_unneeded_entities 選項(xiàng)
–split_sections為每個(gè)源文件的函數(shù)創(chuàng)建一個(gè)section,方便在鏈接的時(shí)候去掉.o文件 中的不用的函數(shù)。–attribute((section(…))) 可以修飾data 和 function,將其放到指定的section,而不是放到默認(rèn)的section
–thumb將該.c文件編譯成 thumb指令,
?
#pragma??arm?????????編譯成arm指令 #pragma??thumb???????編譯成thumb指令 #pragam??push????????保存#pragma?狀態(tài) #pragma??pop?????????彈出狀態(tài)?與上面的可以一起使用 #pragma? pack(n)???設(shè)置 n字節(jié)對(duì)齊,對(duì)于結(jié)構(gòu)體來說。 12345
?
–use_frame_pointer這個(gè)設(shè)置棧頂指針,每次進(jìn)入函數(shù)后,會(huì)首先將棧頂壓入棧,之后再做其他的寄存器壓棧,這樣的好處是backtrace的調(diào)用關(guān)系很容易找出來。詳見ARM開發(fā)中幾個(gè)常見的寄存器詳解
-apcs=interwork 支持內(nèi)部thumb與arm 指令相互切換,比如BLX,這個(gè)支持thumb指令的地方用處較多,
2、armasm
嵌入式匯編
函數(shù)形參列表可以使用變量,但是函數(shù)體必須要用寄存器,函數(shù)體都是匯編語言實(shí)現(xiàn)
需要匯編語言處理返回指令
?
__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; }
?
內(nèi)聯(lián)匯編
同一行如果有多行指令,必須要有封號(hào)(;)
如果一個(gè)指令超出一行,需要增加反斜杠()
在多行格式中,允許在內(nèi)聯(lián)匯編語言塊中的任何位置使用C和C++注釋。但是注釋不能嵌入到多條指令的行中。
在匯編語言中,逗號(hào)(,)用作分隔符,所以C表達(dá)式的逗號(hào)運(yùn)算符必須用括號(hào)括起來來和它們進(jìn)行區(qū)分
標(biāo)簽必須后跟冒號(hào),:,如C和C++標(biāo)簽
asm語句必須位于C++函數(shù)內(nèi)部。asm語句可以在任何需要C++語句的地方使用
內(nèi)聯(lián)程序集代碼中的寄存器名被視為C或C++變量。它們不一定與同名的物理寄存器有關(guān)。如果寄存器未聲明為C或C++變量,編譯器將生成警告
不得在內(nèi)聯(lián)程序集代碼中保存和還原寄存器,編譯器會(huì)執(zhí)行此操作。此外,內(nèi)聯(lián)匯編程序不提供對(duì)物理寄存器的直接訪問。然而,可以通過變量間接訪問寄存器
pc/lr/sp:__current_pc,__current_sp, and __return_address 來read
內(nèi)聯(lián)匯編中不要修改處理器模式或者協(xié)處理器的狀態(tài)
?
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; }
?
由于篇幅原因,后續(xù)再補(bǔ)充armclang的知識(shí)。
審核編輯:湯梓紅
評(píng)論
查看更多