ARM 链接器优化功能介绍
消除公共部分组
链接器可以检测节组的多个副本,并丢弃其他副本。
? Arm Compiler for Embedded 生成用于链接的完整对象。因此:
- 如果 C 和 C++ 源代码中存在内联函数,则每个对象都包含该对象所需的内联函数的外联副本。
- 如果在 C++ 源代码中使用模板,则每个对象都包含对象所需的模板函数。
在公共头文件中声明这些函数时,这些函数可能会在随后链接在一起的单独对象中多次定义。为了消除重复项,编译器将这些函数编译为公共节组的单独实例。
公共节组的单独实例可能不相同。例如,某些副本可能位于使用不同但兼容的构建选项、不同的优化或调试选项构建的库中。
如果副本不相同,则 armlink
?会根据输入对象的属性保留每个公共节组的最佳可用变体。Armlink
?丢弃其余部分。
如果副本相同,则 armlink
?将保留位于的第一个部分组。
您可以使用以下链接器选项来控制此优化:
- 使用 -
-bestdebug
?选项使用最大的公共数据 (COMDAT) 组(可能提供最佳调试视图)。 -
使用 -
-no_bestdebug
?选项使用最小的 COMDAT 组(可能提供最小的代码大小)。这是默认设置。如果使用 -
g
?编译包含 COMDAT 组 A 的所有文件,即使使用 --no_bestdebug
,映像也会更改。
?消除未使用的部分
消除未使用的部分是链接器对图像大小执行的最重要优化。
未使用部分消除:
- 从最终映像中删除无法访问的代码和数据。
- 在可能导致删除所有部分的情况下被禁止显示。
要控制此优化,请使用?armlink
?选项 --remove
、--no_remove
、--first、--last
?和 --keep
。
未使用的部分消除需要一个入口点。因此,如果未为映像指定入口点,请使用?armlink
?选项 --entry
?指定入口点。
使用?armlink
?选项 --info unused
?指示链接器生成它删除的未使用部分的列表。
注意
armlink
?报告错误:L6218E:未定义的符号 <symbol>
,即使未使用的部分删除已删除此符号的要求。此行为与 GNU 链接器?ld
?不同。
在以下情况下,输入部分将保留在最终图像中:
- 它包含一个入口点或一个外部可访问的符号。例如,Arm?v8-M 安全扩展的安全代码中的输入函数。
- 它是
SHT_INIT_ARRAY
、SHT_FINI_ARRAY
或SHT_PREINIT_ARRAY
部分。 - 它被指定为第一个或最后一个输入部分,由 --
first
?或?--last
?选项或分散加载等效项指定。 - 它被?
--keep
?选项标记为不可删除。 - 它直接或间接地由图像中保留的输入部分的非弱引用引用。
- 其名称与输入截面符号所引用的名称匹配,并且该符号引用自图像中保留的截面。
注意
编译器通常将函数和数据收集在一起,并为每个类别发出一个部分。链接器只能删除完全未使用的部分。
您可以使用?
__attribute__(used))
?属性标记源代码中的函数或变量。此属性使?armclang
?为每个函数或变量生成符号?__tagsym$$used.
<num>,其中?<num>
?是用于区分每个符号的计数器。消除未使用的部分不会删除包含?__tagsym$$used.<num>
?的部分。您还可以使用?
armclang
?选项 -ffunction-sections
?来指示编译器为源文件中的每个函数生成一个 ELF 部分。?
使用 RW 数据压缩进行优化
RW 数据区通常包含大量重复值(如零),这使得它们适合压缩。
默认情况下,RW 数据压缩处于启用状态,以最小化 ROM 大小。
链接器压缩数据。然后,在运行时在目标上解压缩此数据。
Arm 库包含一些解压缩算法,链接器选择要添加到映像的最佳算法,以便在执行映像时解压缩数据区域。可以重写链接器选择的算法。
链接器如何选择压缩器
Armlink
?在选择最合适的压缩算法以生成最小图像之前收集有关数据部分内容的信息。
如果压缩合适,armlink
?只能对图像中的所有可压缩数据部分使用一个数据压缩器。可以在这些部分上尝试不同的压缩算法,以生成最佳的整体大小。如果出现以下情况,将自动应用压缩:
Compressed data size + Size of decompressor < Uncompressed data size
选择压缩器后,armlink
?会将解压缩器添加到映像的代码区域。如果最终映像不包含任何压缩数据,则不会添加解压缩程序。
可用于替代链接器使用的压缩算法的选项
链接器具有用于禁用压缩或指定要使用的压缩算法的选项。
可以通过以下任一方式替代链接器使用的压缩算法:
- 使用 -
-datacompressor off
?选项关闭压缩。 - 指定压缩算法。
若要指定压缩算法,请在链接器命令行上使用所需压缩器的编号,例如:
armlink --datacompressor 2 ...
使用命令行选项 --datacompressor list
?获取链接器中可用的压缩算法列表:
armlink --datacompressor list
...
Num Compression algorithm
========================================================
0 Run-length encoding
1 Run-length encoding, with LZ77 on small-repeats
2 Complex LZ77 compression
选择压缩算法时,请注意:
- Compressor 0 在具有大量零字节但非零字节较少的数据上表现良好。
- Compressor 1 在处理非零字节重复的数据时表现良好。
- Compressor 2 在处理包含重复值的数据时表现良好。
链接器首选压缩器 0 或 1,其中数据主要包含零字节 (>75%)。选择 Compressor 2 时,数据包含很少的零字节 (<10%)。如果图像仅由 A32 代码组成,则会自动使用 A32 解压缩器。如果映像包含任何 T32 代码,则使用 T32 解压缩器。如果没有明确的偏好,则对所有压缩机进行测试,以产生最佳的整体尺寸。
?
使用 RW 数据压缩时的注意事项
使用 RW 数据压缩时需要注意一些注意事项。
使用 RW 数据压缩时:
- 使用链接器选项 -
-map
?查看对代码中的区域应用压缩的位置。 - 如果存在从压缩区域到使用加载地址的链接器定义符号的引用,则链接器将关闭 RW 压缩。
- 如果您使用的是带有片上缓存的 Arm? 处理器,请在解压缩后启用缓存,以避免出现代码一致性问题。
压缩数据段在运行时自动解压缩,前提是使用 Arm 库中的代码执行__main
。此代码必须放置在根区域中。最好在散点文件中使用?InRoot$$Sections
?来完成此操作。
如果使用的是散点文件,则可以通过添加?NOCOMPRESS
?属性来指定不压缩加载或执行区域。
与链接器内联的函数
链接器内联功能取决于您指定的选项和输入文件的内容。
链接器可以内联小函数来代替该函数的分支指令。要使链接器能够执行此操作,函数(没有返回指令)必须适合分支指令的四个字节。
使用 --inline
?和 --no_inline
?命令行选项来控制分支内联。但是,--no_inline
?仅关闭用户提供的对象的内联。默认情况下,链接器仍内联 Arm 标准库中的函数。
如果启用了分支内联优化,则链接器会扫描映像中的每个函数调用,然后根据需要进行内联。当链接器找到合适的函数进行内联时,它会将函数调用替换为正在调用的函数中的指令。
链接器在消除任何未使用的部分之前应用分支内联优化,以便在不再调用内联部分时也可以将其删除。
注意
- 对于 Arm?v7-A,链接器可以内联两个 16 位编码的 Thumb 指令,以代替 32 位编码的 Thumb??
BL
?指令。- 对于 Armv8-A 和 Armv8-M,链接器可以内联两个 16 位 T32 指令来代替 32 位 T32?
BL
?指令。
使用 --info=inline
?命令行选项列出所有内联函数。
关于优化到 NOP 的分支
尽管链接器可以将分支替换为?NOP
,但在某些情况下,您可能希望阻止这种情况发生。
默认情况下,链接器将任何分支替换为重新定位,该重定位将解析为具有?NOP
?指令的下一条指令。如果链接器对尾部调用部分重新排序,也可以应用此优化。
但是,在某些情况下,您可能希望禁用该选项,例如,在执行验证或管道刷新时。
要控制此优化,请使用 --branchnop
?和?--no_branchnop
?命令行选项。
尾部调用部分的链接器重新排序
在某些情况下,您可能希望链接器对尾部调用部分重新排序。
尾部调用部分是在该部分末尾包含分支指令的部分。如果分支指令具有以另一个部分开头的函数为目标的重定位,则链接器可以将尾部调用部分放在被调用部分之前。然后,链接器可以将尾部调用部分末尾的分支指令优化为?NOP
?指令。
若要利用此行为,请使用命令行选项 --tailreorder
?将尾部调用部分移动到其目标之前。
使用 --info=tailreorder
?命令行选项可显示有关链接器执行的任何尾调用优化的信息。
对尾部调用部分重新排序的限制
尾部调用部分的重新排序有一些限制。
链接器:
- 对于每个尾部调用目标,只能移动一个尾部调用部分。如果对单个部分有多个尾部调用,则具有相同部分名称的尾部调用部分将移动到目标之前。如果在具有匹配名称的尾部调用部分中找不到部分名称,则链接器将移动它遇到的第一个部分。
- 无法将尾部调用部分移出其执行区域。
- 在内联贴面之前不移动尾部。
合并相同的常量
链接器可以尝试合并以 AArch32 状态为目标的对象中的相同常量。必须使用 Arm? Compiler for Embedded 6 生成对象。如果使用?armclang -ffunction-sections
?选项进行编译,则合并效率更高。此选项是默认选项。
关于此任务
以下过程是显示合并功能的示例。
注意
如果使用散点文件,则任何标有?OVERLAY
?或?PROTECTED
?属性的区域都会影响?armlink --merge_litpools
?选项的行为。
程序
- 创建一个 C 源文件?
litpool.c
,其中包含以下代码:int f1() { return 0xdeadbeef; } int f2() { return 0xdeadbeef; }
- 使用?
-S
?编译源代码以创建汇编文件:armclang -c -S -target arm-arm-none-eabi -mcpu=cortex-m0 -ffunction-sections \ litpool.c -o litpool.s
注意
-ffunction-sections
?是默认值。由于
0xdeadbeef
是一个难以使用指令创建的常量,因此会创建一个文本池,例如:... f1: .fnstart @ BB#0: ldr r0, __arm_cp.0_0 bx lr .p2align 2 @ BB#1: __arm_cp.0_0: .long 3735928559 @ 0xdeadbeef ... .fnend ... .code 16 @ @f2 .thumb_func f2: .fnstart @ BB#0: ldr r0, __arm_cp.1_0 bx lr .p2align 2 @ BB#1: __arm_cp.1_0: .long 3735928559 @ 0xdeadbeef ... .fnend ...
注意
每个函数都有一个常量副本,因为?armclang
?不能在两个函数之间共享这些常量。 - 编译源代码以创建对象:
armclang -c -target arm-arm-none-eabi -mcpu=cortex-m0 litpool.c -o litpool.o
- 使用?
--merge_litpools
?选项链接目标文件:armlink --cpu=Cortex-M0 --merge_litpools litpool.o -o litpool.axf
注意
--merge_litpools
?是默认值。 - 运行?
fromelf
?查看镜像结构:fromelf -c -d -s -t -v -z litpool.axf
以下示例显示了合并的结果:
... f1 0x00008000: 4801 .H LDR r0,[pc,#4] ; [0x8008] = 0xdeadbeef 0x00008002: 4770 pG BX lr f2 0x00008004: 4800 .H LDR r0,[pc,#0] ; [0x8008] = 0xdeadbeef 0x00008006: 4770 pG BX lr $d.4 __arm_cp.1_0 0x00008008: deadbeef .... DCD 3735928559 ...
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!