一、說在前面的話
時間大約在2015年,Arm第一次在 MDK 5.20 中引入了Arm Compiler6(那時候的版本是 6.9),正式拉開了Arm官方編譯器從第五版(armcc)到第六版(armclang)升級替換的序幕……
嵌入式行業的長尾效應是及其突出的,且不說都2022年了還有很多人在堅持 MDK4,即便是從“Arm在2017年對外宣布停止維護 Arm Compiler 5”算起,如今5年過去了,堅持使用 armcc 的用戶仍然不在少數。
Arm Compiler 5,也就是大家口中的 armcc,它很弱么?相對免費的工具鏈 arm gcc來說,它還是強很明顯;但你要說它非常能打么?作為一個“理論上”收費的編譯器,它甚至已經全方位落后于最新發布的“免費開源”編譯器LLVM Embedded ToolChain For Arm 14.0.0(clang),更不用說現在的當紅貴人Arm Compiler 6(armclang)了。
如果非要我給出一份“不負責任”的編譯器性能對比的話,這是獨屬于我的答案:
arm gcc
別問我為什么,問就是誰用誰知道。 如果不是因為產品存在 Golden Code(屎山),只要你選定了Arm Compiler 而不是IAR,既然橫豎要使用付費編譯器,為什么不用Arm例行維護(幾乎每半年不到就發布一個新版本)的Arm Compiler 6,而繼續死守Arm Compiler 5呢? 有的人說“Arm Compiler 6不如Arm Compiler 5”穩定。這里給出我的幾個反駁的幾個理由,但我不指望能說服那些抱有這類想法的人:
Arm Compiler 5已經停止維護,Arm Compiler 6還在持續更新。沒有bug的編譯器是不存在的,一個生命周期已經結束的編譯器就幾乎不在存在修復已有bug和未發現bug的可能性;而一個積極維護的編譯器則可以及時的將發現的問題進行修復;
Arm Compiler 5過去只有Arm維護,而 Arm Compiler 6是基于LLVM(clang)的商業化改進版,這里LLVM是一個開源項目,由眾多的個人和商業組織共同維護,參考過去gcc的成功——這么多“大聰明”在盯著的項目,即便發現錯誤,估計也是“分分鐘”就被拿去“邀功請賞”了吧?
雖然我在實際使用中抓到(報告并得到修復)的Arm Compiler 6 bug的數量超過在座99%的人,但正因如此,我知道要遇到一個Arm Compiler 6的bug有多難——更多時候,其實是我們自己對編譯器理解不深刻,甚至是基于自己對C語法的錯誤認知導致的“烏龍”。在我看來,與其懷疑Arm Compiler 6不穩定,不如懷疑下自己對C語言的理解。
不要屈服于由“未知帶來的恐懼”,不要拿“污名化”當做掩蓋自己“偷懶和無知”的遮羞布(對這句話感到憤怒的人,我送你一句:愛聽不聽,歡迎取關,謝謝)。
看到這里,如果你決定繼續往下閱讀,我就假設你已經有興趣去嘗試使用Arm Compiler 6 來逐步取代已有的 Arm Compiler 5了。基于這一前提,我們將用隨后的一系列文章來介紹:
短期內:MDK 5.37 拋棄 armcc 的補救措施
中期:從 armcc 向 armclang 進行過渡時期的一些快速應對的方法
面對一些armcc 獨有的編譯器特性的應對方法吧
二、臨時補救
雖然最新的 MDK拋棄了Arm Compiler 5,但它仍然允許我們通過手動添加的方法將其請回來,具體方法我在《驚爆內幕:老MDK也可以使用新編譯器》文章中已經詳細介紹過,這里就不再贅述,值得補充說明的是:
1、新MDK也可以手工添加老版本的編譯器,不要被文章的標題限制住了思路
三、幾顆定心丸
1.“街頭霸王”
我們知道MDK是一個集成開發環境(Integrated Development Environment),它默認原生支持Arm Compiler 5(armcc)、Arm Compiler 6(armclang)和 arm gcc。雖然這三個編譯器都是由Arm所維護和提供的,但前兩者算是彼此兼容的編譯器:
使用共同的armlink
使用相同的方式來描述地址空間布局(分散加載腳本 scatter script)
從Arm Compiler 6.14開始,armclang甚至開始支持armasm的匯編語法了
實際上可以認為,armcc和armclang是一對連體兄弟,身子是armlink,而兩個腦袋分別是 armcc 和 armclang。大約是這種感覺,你體會下。
作為定心丸的結論是:
原來Arm Compiler 5 項目下的所有庫(*.lib)都可以在 Arm Compiler 6下直接使用
原來由 Arm Compiler 5 生成的對象文件(*.o)都可以在 Arm Compiler 6下直接使用
原來 Arm Compiler 5下所用到的“幾乎所有” armlink 相關的特性都可以在 Arm Compiler 6 直接使用(因為基本就是同一個armlink,所以幾乎不存在“移植”的說法)
當然,還是有一些特例的,比如 __attribute__((at(地址))) 語法,這個我們將出一個專題來介紹應對方式。
2.“偷懶是第一生產力”
由于 Arm Compiler 6 脫胎于LLVM,因此在匯編語法上它也繼承了 clang 的特性——使用 GNU Assembly Syntax,而非 Arm 此前一直嘗試推廣的 Unified Assembly Language(UAL)匯編語法。
由于 Arm Compiler 5 一直使用的是 UAL 匯編語法,廣大用戶長時間來積累了大量使用該語法編寫的 .s 文件。
匯編原本就是個頭疼的東西——不到萬不得已誰寫匯編啊?對很多項目來說,且不說匯編原本就是少數大牛才敢碰的東西——幾乎就是“Golden Code(屎山)”的代名詞,實際上,這些“歷史塵埃”的作者可能早就已經離職了——就算你把本人找回來,恐怕很多時候連當事人自己也是狗咬刺猬無法下嘴了。
盡管 Arm 專門寫了一個名為《Migrating from armasm to the armclang Integrated Assembler》的文檔來“教大家做事”,但社區的反饋可想而知……
在眾多“我不想,你求我啊……”的聲音中,Arm Compiler 6從 6.14版本開始,重新把 UAL 的支持加了回來,并在 MDK 中引入了這樣一個選項:
這里幾個選項的意義如下:
armclang(Auto Select):使用 armclang 來編譯匯編源代碼(對應命令行選項-masm=auto),然后armclang會根據語法風格自動決定是當做 GNU Assembly Syntax 來處理,還是使用 UAL 語法來解析。我吐血推薦使用這個選項。
armclang (GNU Syntax):使用armclang來編譯匯編源代碼(對應命令行選項-masm=gnu),然后強制使用 GNU 匯編語法風格。
armclang(Arm Syntax):使用armclang來編譯匯編源代碼(對應命令行選項-masm=armasm),然后強制使用 UAL 匯編語法風格。
其實,這里armclang也是個二道販子——它也是調用armasm來完成編譯的,只不過在這之前,它會默認用C預編譯器對匯編源代碼進行預處理,換句話說,折磨armasm很多年的“如何在匯編代碼中使用C語言宏和預處理”的問題,得到了根治——你可以大大方方的在匯編代碼里用 #include、各類宏定義和 #if 了。
armasm(Arm Syntax):直接使用armasm來編譯匯編源代碼。該選項對 老的 UAL 源代碼文件兼容性最好。如果使用armclang(Arm Syntax)遇到問題,不妨用這個選項來試一下——一般都可以順利解決問題。
怎么樣,不用修改屎山了,是不是如釋重負?
3.在線匯編(InlineAssembly)和嵌入C代碼的匯編(Embedded Assembly)
無論你是否了解 Arm Compiler 5所支持的這兩種在C語言中使用匯編的方法,也不用關心它們的區別,結論是——任何Arm Compiler 5下的C代碼只要使用了上述兩種方法之一,基本上就是“需要手工干預”的。 這里我給出一個萬能藥方: 對這部分C源文件,請使用 armcc 編譯,生成 .o 后扔到 Arm Compiler 6里直接參與鏈接即可。 當然,如果你有興趣依照前面文檔里的介紹進行改寫,我祝你好胃口。
至于如何讓改寫后的C代碼同時兼容 Arm Compiler 5 和 Arm Compiler 6,就離不開下面的內容了——它也是我們后續一系列差異化改造的基礎。
四、如何檢測編譯器
一般來說,當我們要對某一部分代碼進行跨編譯器移植的時候,當然可以按照新語法一改了之,但對很多人來說,老的編譯器總是會讓大家萌生一種說不上來的留念之情,繼而抱有:
“我要讓修改后的代碼仍然兼容過去老編譯器”; 或是: “老代碼刪除太可惜了,我要留下來,以后萬一有用呢?” 這樣的想法。我也是這么想的。
要做到這一點,就繞不開一個核心問題:如何可靠的檢測出當前編譯器版本呢?
一般來說,編譯器的宏檢測有兩個思路:
借助某一編譯器獨有的特征宏來判斷編譯器
借助多個編譯器共有但值不同的宏來判斷
對于第一種思路,有兩個比較有名的宏:__GNUC__和__clang__。過去,很多人喜歡用下面的代碼來判斷編譯環境是否是GCC或者CLANG:
#if defined(__GNUC__) /* 我覺得編譯器gcc */ #endif #ifdefined(__clang__) /*我覺得編譯器是 clang */ #endif然而,遺憾的是,由于很多編譯器都在某種程度上對 GCC 擴展提供支持,因而也會定義宏__GNUC__,比如 armcc、armclang、clang、IAR都定義了該宏……因此,它幾乎失去了GCC特征宏的價值,退化為“當前編譯器支持GCC擴展(但具體哪些GCC擴展,這就看我心情了)”的標志。
其實__clang__ 宏也是類似的情況,因為 armclang 也會定義該宏,畢竟Arm Compiler 6是從LLVM中派生而出的。
當然,更為常見和有用的編譯器特征宏是 __IAR_SYSTEMS_ICC__ ,借助它的幫助,我們可以判斷當前開發環境是否為 IAR:
//! ote for IAR #undef __IS_COMPILER_IAR__ #if defined(__IAR_SYSTEMS_ICC__) # define __IS_COMPILER_IAR__ 1 #endifArm Compiler 5 和 Arm Compiler 6 都是 Arm Compiler,區別它們二者有很多方法,但官方推薦的方法是判斷宏__ARMCC_VERSION 的值。
從名字上就可以看出,這是一個自 armcc 以來一直延續到 armclang 的共有宏,它保存了編譯器的版本,因此我們很容易編寫出如下的宏:
//! ote for arm compiler 5 #undef __IS_COMPILER_ARM_COMPILER_5__ #if ((__ARMCC_VERSION >= 5000000) && (__ARMCC_VERSION < 6000000)) # define __IS_COMPILER_ARM_COMPILER_5__ 1 #endif //! @} //! ote for arm compiler 6 #undef __IS_COMPILER_ARM_COMPILER_6__ #if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) # define __IS_COMPILER_ARM_COMPILER_6__ 1 #endif #undef __IS_COMPILER_ARM_COMPILER__ #if defined(__IS_COMPILER_ARM_COMPILER_5__) && __IS_COMPILER_ARM_COMPILER_5__ || defined(__IS_COMPILER_ARM_COMPILER_6__) && __IS_COMPILER_ARM_COMPILER_6__ # define __IS_COMPILER_ARM_COMPILER__ 1 #endif借助它們的幫助,我們可以很容易的通過判斷 __IS_COMPILER_ARM_COMPILER_5__ 和 __IS_COMPILER_ARM_COMPILER_6__ 的值是否為“1”來確定當前的編譯器版本。
在只關心當前編譯器是否為Arm Compiler,而不在乎它具體是哪個版本時,可以借助 __IS_COMPILER_ARM_COMPILER__ 來進行判斷。
假設我們的代碼只考慮支持 gcc、clang、iar、armcc和armclang,那么利用排除法,我們就可以輕松的判斷當前編譯環境是否是 GCC 或 LLVM了:
#undef __IS_COMPILER_LLVM__ #if defined(__clang__) && !__IS_COMPILER_ARM_COMPILER_6__ # define __IS_COMPILER_LLVM__ 1 #else //! ote for gcc # undef __IS_COMPILER_GCC__ # if defined(__GNUC__) && !( defined(__IS_COMPILER_ARM_COMPILER__) ||defined(__IS_COMPILER_LLVM__) ||defined(__IS_COMPILER_IAR__)) # define __IS_COMPILER_GCC__ 1 # endif //! @} #endif
簡單說一下這里的思路:
1、在排除了 Arm Compiler 6 的前提下,根據 __clang__來判斷當前編譯器是否為 LLVM(即:__IS_COMPILER_LLVM__);
2、在排除了 LLVM、Arm Compiler和IAR的前提下,根據 __GNUC__ 來判斷當前編譯器是否為GCC
為了方便大家理解,下面介紹幾個上述宏的應用場景:
如何在 Arm Compiler 6下告知編譯器 main() 函數不帶輸入參數
默認情況下(使用默認的 libc),Arm Compiler 6會認為 main() 函數是帶有標準的輸入參數的:
int main (int argc, char *argv[]);
哪怕你強行把 main() 函數寫成無需輸入參數的情況,編譯器也還是會準備好參數——而準備參數的過程很有可能會導致 hardfault(這里會涉及到semihosting的問題,比較頭疼,暫時不表)。
為了解決這一問題,我們一般這么做:
#if__IS_COMPILER_ARM_COMPILER_6__ __asm(".global__ARM_use_no_argv "); #endif
又因為 MicroLib 不存在該問題,因為我們可以根據(MDK會追加的一個宏)__MICROLIB,來做一個小小的區分:
#if__IS_COMPILER_ARM_COMPILER_6__ # ifndef __MICROLIB __asm(".global __ARM_use_no_argv "); #endif #endif
也就是當且僅當我們使用 Arm Compiler 6,且不使用MicroLib的時候,通過專門的語法結構來告訴編譯器:main() 函數沒有傳入參數。
如何關閉 Semihosting
你有沒有遇到過這樣神奇的情景:在調試模式下,程序可以正常運行;一旦退出調試模式,系統就死機了,重新進入調試模式后,發現系統進入了Hardfault。
恭喜你,這很可能就是(默認開啟的)semihosting 在作怪。關于Semihosting的內容,篇幅過大,不在本文討論之列。
今天我們只介紹一下如何關閉它。
Arm Compiler 5和Arm Compiler 6關閉 Semihosting的方法是不同的:
#if __IS_COMPILER_ARM_COMPILER_6__ __asm(".global __use_no_semihosting"); #elif __IS_COMPILER_ARM_COMPILER_5__ #pragmaimport(__use_no_semihosting) #endif一旦關閉了 Semihosting,Arm Compiler 6 就可能會報告類似如下的錯誤:
Error:L6915E:Libraryreportserror:__use_no_semihostingwasrequested,but_sys_exitwasreferenced簡單解釋下原因:Arm Compiler 6 依賴的一個函數 _sys_exit()原本是用Semihosting方式默認提供的,現在你把 Semihosting 關閉了,所以你要負責到底。
知道了原因,解決方法也很簡單——缺這個函數,我們提供一個就行:
#if __IS_COMPILER_ARM_COMPILER_6__ void _sys_exit(int ret) { (void)ret; while(1) {} } #endif類似的情況還會發生在一個叫 _ttywrch() 的函數上,我們可以如法炮制:
/*為armcompiler5 和 arm compiler 6 都添加這個空函數 */ #if__IS_COMPILER_ARM_COMPILER__ void _ttywrch(int ch) { ARM_2D_UNUSED(ch); } #endif
如何解決使用 assert.h引發的問題
很多代碼都有使用 assert() 來截獲錯誤的習慣,當我們使用 Arm Compiler 6 且開啟 MicroLib的時候,由于 MicroLib并不提供對 assert() 底層函數的具體實現,當我們沒有定義 NDEBUG 來關閉 assert() 時,會在鏈接階段看到如下的編譯錯誤:
Error: L6218E: Undefined symbol __aeabi_assert (referred from main.o).
知道原因后,解決也很簡單:既然MicroLib沒提供實現,我們就自己提供一個好了:
#if__IS_COMPILER_ARM_COMPILER_6__ && defined(__MICROLIB) void __aeabi_assert(const char *chCond, const char *chLine, int wErrCode) { (void)chCond; (void)chLine; (void)wErrCode; while(1) { __NOP(); } } #endif
既然上述這套 __IS_COMPILER_xxxx__ 這么好用,我們可以從哪里獲得呢?
目前已知的獲取渠道包括但不限于:
從本文抄下來
包含獲取perf_counter 并包含perf_counter.h
在存在 arm-2d 的情況下,直接包含 arm_2d.h或者 arm_2d_utils.h
……
五、說在后面的話
我承認Arm Compiler 5遷移到Arm Compiler 6不是一個輕松的過程,但也絕非大家想象的那樣痛苦,很多時候,也許只是在MDK中更換一個選項那么簡單:
不試一試怎么知道呢?
對主流芯片大廠,比如 ST和NXP來說,它們的庫早就完成了對 Arm Compiler 6的支持,可以說如果你遇到編譯器兼容問題,應該首先考慮下載最新版本的驅動庫。
本文介紹的方法,基本上可以應對常見的從Arm Compiler 5到 Arm Compiler 6可能遇到的問題。
這當然不是一份萬能的解藥,對于一些特殊的情況,我們將在后續文章中進行專題討論。
審核編輯:劉清
-
ARM
+關注
關注
134文章
9104瀏覽量
367792 -
GCC
+關注
關注
0文章
107瀏覽量
24850 -
編譯器
+關注
關注
1文章
1634瀏覽量
49153 -
MDK
+關注
關注
4文章
209瀏覽量
32079 -
GNU
+關注
關注
0文章
143瀏覽量
17507
原文標題:【反復橫跳】從AC5到AC6轉型之路(1)——補救和準備
文章出處:【微信號:Ithingedu,微信公眾號:安芯教育科技】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論