如何防止函数在编译 X86 时对齐到 16 字节边界?



我正在一个类似嵌入式的环境中工作,其中每个字节都非常珍贵,比未对齐访问的额外周期要珍贵得多。我有一些来自操作系统开发示例的简单 Rust 代码:

#![feature(lang_items)]
#![no_std]
extern crate rlibc;
#[no_mangle]
pub extern fn rust_main() {
// ATTENTION: we have a very small stack and no guard page
let hello = b"Hello World!";
let color_byte = 0x1f; // white foreground, blue background
let mut hello_colored = [color_byte; 24];
for (i, char_byte) in hello.into_iter().enumerate() {
hello_colored[i*2] = *char_byte;
}
// write `Hello World!` to the center of the VGA text buffer
let buffer_ptr = (0xb8000 + 1988) as *mut _;
unsafe { *buffer_ptr = hello_colored };
loop{}
}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! {loop{}}

我也使用这个链接器脚本:

OUTPUT_FORMAT("binary")
ENTRY(rust_main)
phys = 0x0000;
SECTIONS
{
.text phys : AT(phys) {
code = .;
*(.text.start);
*(.text*)
*(.rodata)
. = ALIGN(4);
}
__text_end=.;
.data : AT(phys + (data - code))
{
data = .;
*(.data)
. = ALIGN(4);
}
__data_end=.;
.bss : AT(phys + (bss - code))
{
bss = .;
*(.bss)
. = ALIGN(4);
}
__binary_end = .;
}

我使用 i586 目标编译器和 GNU ld 链接器(包括链接器命令中的-O3(使用opt-level: 3和 LTO 对其进行优化。我还在链接器上尝试了opt-level: z和耦合-Os,但这导致代码更大(它没有展开循环(。就目前而言,尺寸似乎很合理opt-level: 3.

有相当多的字节似乎浪费在将函数对齐到某个边界上。展开循环后,插入 7 条nop指令,然后按预期进行无限循环。在此之后,似乎还有另一个无限循环,前面有 7 个 16 位覆盖nop指令(即,xchg ax,ax而不是xchg eax,eax(。这加起来在 196 字节的平面二进制文件中浪费了大约 26 个字节。

  • 优化器在这里到底在做什么?
  • 我有什么选项可以禁用它?
  • 为什么二进制文件中包含无法访问的代码?

完整的组件列表如下:

0:   c6 05 c4 87 0b 00 48    movb   $0x48,0xb87c4
7:   c6 05 c5 87 0b 00 1f    movb   $0x1f,0xb87c5
e:   c6 05 c6 87 0b 00 65    movb   $0x65,0xb87c6
15:   c6 05 c7 87 0b 00 1f    movb   $0x1f,0xb87c7
1c:   c6 05 c8 87 0b 00 6c    movb   $0x6c,0xb87c8
23:   c6 05 c9 87 0b 00 1f    movb   $0x1f,0xb87c9
2a:   c6 05 ca 87 0b 00 6c    movb   $0x6c,0xb87ca
31:   c6 05 cb 87 0b 00 1f    movb   $0x1f,0xb87cb
38:   c6 05 cc 87 0b 00 6f    movb   $0x6f,0xb87cc
3f:   c6 05 cd 87 0b 00 1f    movb   $0x1f,0xb87cd
46:   c6 05 ce 87 0b 00 20    movb   $0x20,0xb87ce
4d:   c6 05 cf 87 0b 00 1f    movb   $0x1f,0xb87cf
54:   c6 05 d0 87 0b 00 57    movb   $0x57,0xb87d0
5b:   c6 05 d1 87 0b 00 1f    movb   $0x1f,0xb87d1
62:   c6 05 d2 87 0b 00 6f    movb   $0x6f,0xb87d2
69:   c6 05 d3 87 0b 00 1f    movb   $0x1f,0xb87d3
70:   c6 05 d4 87 0b 00 72    movb   $0x72,0xb87d4
77:   c6 05 d5 87 0b 00 1f    movb   $0x1f,0xb87d5
7e:   c6 05 d6 87 0b 00 6c    movb   $0x6c,0xb87d6
85:   c6 05 d7 87 0b 00 1f    movb   $0x1f,0xb87d7
8c:   c6 05 d8 87 0b 00 64    movb   $0x64,0xb87d8
93:   c6 05 d9 87 0b 00 1f    movb   $0x1f,0xb87d9
9a:   c6 05 da 87 0b 00 21    movb   $0x21,0xb87da
a1:   c6 05 db 87 0b 00 1f    movb   $0x1f,0xb87db
a8:   90                      nop
a9:   90                      nop
aa:   90                      nop
ab:   90                      nop
ac:   90                      nop
ad:   90                      nop
ae:   90                      nop
af:   90                      nop
b0:   eb fe                   jmp    0xb0
b2:   66 90                   xchg   %ax,%ax
b4:   66 90                   xchg   %ax,%ax
b6:   66 90                   xchg   %ax,%ax
b8:   66 90                   xchg   %ax,%ax
ba:   66 90                   xchg   %ax,%ax
bc:   66 90                   xchg   %ax,%ax
be:   66 90                   xchg   %ax,%ax
c0:   eb fe                   jmp    0xc0
c2:   66 90                   xchg   %ax,%ax

正如 Ross 所说,将函数和分支点对齐为 16 字节是英特尔推荐的常见 x86 优化,尽管它偶尔会效率较低,例如在您的情况下。 对于编译器来说,以最佳方式决定是否对齐是一个难题,我相信LLVM只是选择始终对齐。 查看有关 x86-64 程序集的性能优化 - 对齐和分支预测的详细信息。

正如red75prime的注释所暗示的那样(但没有解释(,LLVM使用align-all-blocks的值作为分支点的字节对齐方式,因此将其设置为1将禁用对齐。 请注意,这适用于全球,建议使用比较基准。

最新更新