链接器可以检测节组的多个副本,并丢弃其他副本。
® Arm Compiler for Embedded 生成用于链接的完整对象。因此:
在公共头文件中声明这些函数时,这些函数可能会在随后链接在一起的单独对象中多次定义。为了消除重复项,编译器将这些函数编译为公共节组的单独实例。
公共节组的单独实例可能不相同。例如,某些副本可能位于使用不同但兼容的构建选项、不同的优化或调试选项构建的库中。
如果副本不相同,则 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:未定义的符号
,即使未使用的部分删除已删除此符号的要求。此行为与 GNU 链接器ld
不同。
在以下情况下,输入部分将保留在最终图像中:
SHT_INIT_ARRAY
、SHT_FINI_ARRAY
或SHT_PREINIT_ARRAY
部分。first
或 --last
选项或分散加载等效项指定。--keep
选项标记为不可删除。注意
编译器通常将函数和数据收集在一起,并为每个类别发出一个部分。链接器只能删除完全未使用的部分。
您可以使用
__attribute__(used))
属性标记源代码中的函数或变量。此属性使armclang
为每个函数或变量生成符号__tagsym$$used.
,其中 是用于区分每个符号的计数器。消除未使用的部分不会删除包含
__tagsym$$used.
的部分。您还可以使用
armclang
选项 -ffunction-sections
来指示编译器为源文件中的每个函数生成一个 ELF 部分。
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
选择压缩算法时,请注意:
链接器首选压缩器 0 或 1,其中数据主要包含零字节 (>75%)。选择 Compressor 2 时,数据包含很少的零字节 (<10%)。如果图像仅由 A32 代码组成,则会自动使用 A32 解压缩器。如果映像包含任何 T32 代码,则使用 T32 解压缩器。如果没有明确的偏好,则对所有压缩机进行测试,以产生最佳的整体尺寸。
使用 RW 数据压缩时需要注意一些注意事项。
使用 RW 数据压缩时:
-map
查看对代码中的区域应用压缩的位置。压缩数据段在运行时自动解压缩,前提是使用 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
指令的下一条指令。如果链接器对尾部调用部分重新排序,也可以应用此优化。
但是,在某些情况下,您可能希望禁用该选项,例如,在执行验证或管道刷新时。
要控制此优化,请使用 --branchnop
和 --no_branchnop
命令行选项。
在某些情况下,您可能希望链接器对尾部调用部分重新排序。
尾部调用部分是在该部分末尾包含分支指令的部分。如果分支指令具有以另一个部分开头的函数为目标的重定位,则链接器可以将尾部调用部分放在被调用部分之前。然后,链接器可以将尾部调用部分末尾的分支指令优化为 NOP
指令。
若要利用此行为,请使用命令行选项 --tailreorder
将尾部调用部分移动到其目标之前。
使用 --info=tailreorder
命令行选项可显示有关链接器执行的任何尾调用优化的信息。
尾部调用部分的重新排序有一些限制。
链接器:
链接器可以尝试合并以 AArch32 状态为目标的对象中的相同常量。必须使用 Arm® Compiler for Embedded 6 生成对象。如果使用 armclang -ffunction-sections
选项进行编译,则合并效率更高。此选项是默认选项。
以下过程是显示合并功能的示例。
注意
如果使用散点文件,则任何标有OVERLAY
或PROTECTED
属性的区域都会影响armlink --merge_litpools
选项的行为。
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
...