0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

介绍从AC5到AC6转型之路

安芯教育科技 来源:裸机思维 2023-03-03 09:11 次阅读

一、说在前面的话

时间大约在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 < armcc < clang < IAR <= armclang

别问我为什么,问就是谁用谁知道。 如果不是因为产品存在 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汇编语法了

实际上可以认为,armccarmclang是一对连体兄弟,身子是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 中引入了这样一个选项:

fd0d16c8-b8e1-11ed-bfe3-dac502259ad0.png

这里几个选项的意义如下:

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 5Arm 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
#endif
Arm 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、armccarmclang,那么利用排除法,我们就可以轻松的判断当前编译环境是否是 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 CompilerIAR的前提下,根据 __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 5Arm 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
一旦关闭了 SemihostingArm 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中更换一个选项那么简单:

fdba4d34-b8e1-11ed-bfe3-dac502259ad0.png

不试一试怎么知道呢?

对主流芯片大厂,比如 ST和NXP来说,它们的库早就完成了对 Arm Compiler 6的支持,可以说如果你遇到编译器兼容问题,应该首先考虑下载最新版本的驱动库

本文介绍的方法,基本上可以应对常见的从Arm Compiler 5Arm Compiler 6可能遇到的问题。

这当然不是一份万能的解药,对于一些特殊的情况,我们将在后续文章中进行专题讨论。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM
    ARM
    +关注

    关注

    134

    文章

    9043

    浏览量

    366748
  • GCC
    GCC
    +关注

    关注

    0

    文章

    105

    浏览量

    24818
  • 编译器
    +关注

    关注

    1

    文章

    1618

    浏览量

    49044
  • MDK
    MDK
    +关注

    关注

    4

    文章

    209

    浏览量

    32032
  • GNU
    GNU
    +关注

    关注

    0

    文章

    143

    浏览量

    17475

原文标题:【反复横跳】从AC5到AC6转型之路(1)——补救和准备

文章出处:【微信号:Ithingedu,微信公众号:安芯教育科技】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    STM32CubeMX生成FreeRTOS的MDK工程不支持AC6编译器吗?

    使用STM32CubeMX生成FreeRTOS的MDK工程,选择AC5编译器可以编译成功,选择AC6编译器有很多错误,是STM32CubeMX生成FreeRTOS的MDK工程还不支持AC6编译器吗?什么时候会支持呢?
    发表于 03-06 08:24

    printf在keil5AC6编译器下运行进入死循环的原因?

    Initialization ...n\");把信息显示在屏幕上,用AC5编译时运行正常,但是编译速度太慢;而用AC6编译时,速度快,但运行出现死循环(更改库编译都能通过),这个宏定义
    发表于 04-07 06:46

    如何正确导入和操作AC6

    这听起来很愚蠢。 SDK 4.3没有* .a文件吗? 如何正确导入和操作AC6? 编译时我有以下错误: 做所有'构建目标:STM32F30x_UserProject.elf''调用:MCU GCC
    发表于 07-02 09:22

    FAQ0115 AT32使用AC6编译器注意事项

    image size, 而手动选择了 AC5 的默认优化等级-O0 导致,调整为默认优化等级可解决。情形二 堆栈需求增加使用 AC6 编译器 –O0 优化等级时, n 级条件表达式可能会产生巨大的栈需求
    发表于 05-25 19:57

    介绍ThreadX GUIX的MDK AC5方式移植和设计框架

    第7章 ThreadX GUIX移植STM32F429(MDK AC5)本章节将为大家介绍ThreadX GUIX的MDK AC5方式移植和设计框架,理论上不建议初学者直接学习,因为
    发表于 08-06 09:52

    ThreadX内核的MDK AC5方式移植和设计框架

    第4章 ThreadX操作系统移植(MDK AC5)本章节将为大家介绍ThreadX内核的MDK AC5方式移植和设计框架,理论上不建议初学者直接学习,因为本章节涉及的知识点很多,建
    发表于 08-11 08:23

    RTX5内核的AC6编译器移植

    5章 RTX5操作系统移植(MDK AC6)本章教程为大家讲解RTX5内核的AC6编译器移植。目录第5
    发表于 08-11 07:41

    使用AC6编译会报错,有没有兼容AC6的标准库?

    使用AC6编译会报错有没有兼容AC6的标准库?
    发表于 06-01 06:06

    AC6编译器出现ArmClang的问题该怎样去解决呢

    使用工程的属性配置【即:使用工具栏的魔法棒配置】关于AC6的编译速度去掉Browse Information选项这种情况下个人感觉并没有和AC5差距很大,AC5可能还快一点勾选Browse
    发表于 06-20 14:18

    IMXRT1052 AC5切换到AC6编译遇到的问题与解决办法

    最近在搞IMXRT1052,下载了最新的SDK(SDK_2_9_1_MIMXRT1052xxxxB),这个SDK默认是AC6编译的。而AC6在编译时又比AC5快了很多,所以打算切换到AC6
    发表于 08-02 15:55

    imxrt1052 AC5切换到AC6的遇到的问题及其解决办法

    最近在搞IMXRT1052,下载了最新的SDK(SDK_2_9_1_MIMXRT1052xxxxB),这个SDK默认是AC6编译的。而AC6在编译时又比AC5快了很多,所以打算切换到AC6
    发表于 11-10 14:38

    AC5AC6的两面包夹芝士堆栈模型设计方案讨论

    1、AC5AC6转型之路——“两面包夹芝士”的堆栈模型  栈(Stack)“是我们用来分配
    发表于 11-16 15:18

    关于MDK编译器AC5AC6切换的问题解析

    如上图所示,作者将一个工程AC5切换到AC6后,出现编译报错。原因是在AC5环境下,部分源文件 #include "cmsis_armcc.h"头文件,此头文件是
    发表于 12-19 16:44

    光纤路由腾达AC6升级上市,给你更高速更智能的上网体验

    AC6外形是一样的,4根棱角分明的5dBi高增益刀锋天线,WiFi信号依旧强劲。 CPU主频升至1GHz 升级版AC6 的CPU主频原来的900MHz升级至1GHz,运行速度提升20
    发表于 09-19 13:40 1526次阅读

    关于MDK编译器AC5AC6切换的问题

    如上图所示,作者将一个工程AC5切换到AC6后,出现编译报错。
    的头像 发表于 10-16 14:16 3354次阅读
    关于MDK编译器<b class='flag-5'>AC5</b>与<b class='flag-5'>AC6</b>切换的问题