如何在Delphi汇编程序中协调短条件跳转与分支目标对齐?
我正在使用Delphi版本10.2 Tokyo进行32位和64位汇编,完全使用汇编编写一些函数。
如果我不使用.align
,编译器会正确编码short
条件跳转指令(2字节指令,包含1字节操作码074h
和1字节相对偏移 - +高达07Fh)。但是,如果我连一个.align
,甚至小到.align 4
- 所有条件跳转指令都位于.align之前,目的地位于.align
之后 - 在此case所有这些指令都成为6字节指令,而不是2字节指令。只有位于.align之后的指令才能正确编码为2字节short
。
Delphi Assembler不接受'短'前缀。
如何在Delphi汇编程序中将短条件跳转与分支目标对齐与.align
进行协调?
以下是一个示例程序 - 请注意中间有一个.align
。
procedure Test; assembler;
label
label1, label2, label3;
asm
mov al, 1
cmp al, 2
je label1
je label2
je label3
label1:
mov al, 3
cmp al, 4
je label1
je label2
je label3
mov al, 5
.align 4
label2:
cmp al, 6
je label1
je label2
je label3
mov al, 7
cmp al, 8
je label1
je label2
je label3
label3:
end;
以下是它的编码方式 - 位于align
之前的条件跳转指向label2和label3(在align
之后)被编码为6字节指令(这是64位CPU目标):
0041C354 B001 mov al,$01 // mov al, 1
0041C356 3C02 cmp al,$02 // cmp al, 2
0041C358 740C jz $0041c366 // je label1
0041C35A 0F841C000000 jz $0041c37c // je label2
0041C360 0F8426000000 jz $0041c38c // je label3
0041C366 B003 mov al,$03 //label1: mov al, 3
0041C368 3C04 cmp al,$04 // cmp al, 4
0041C36A 74FA jz $0041c366 // je label1
0041C36C 0F840A000000 jz $0041c37c // je label2
0041C372 0F8414000000 jz $0041c38c // je label3
0041C378 B005 mov al,$05 // mov al, 5
0041C37A 8BC0 mov eax,eax // <-- a 2-byte dummy instruction, inserted by ".align 4" (almost a 2-byte NOP)
0041C37C 3C06 cmp al,$06 //label2: cmp al, 6
0041C37E 74E6 jz $0041c366 // je label1
0041C380 74FA jz $0041c37c // je label2
0041C382 7408 jz $0041c38c // je label3
0041C384 B007 mov al,$07 // mov al, 7
0041C386 3C08 cmp al,$08 // cmp al, 8
0041C388 74DC jz $0041c366 // je label1
0041C38A 74F0 jz $0041c37c // je label2
0041C38C C3 ret // label3:
但如果删除.align
- 所有指令的大小都正确 - 只需要2个字节:
0041C354 B001 mov al,$01 // mov al, 1
0041C356 3C02 cmp al,$02 // cmp al, 2
0041C358 7404 jz $0041c35e // je label1
0041C35A 740E jz $0041c36a // je label2
0041C35C 741C jz $0041c37a // je label3
0041C35E B003 mov al,$03 //label1: mov al, 3
0041C360 3C04 cmp al,$04 // cmp al, 4
0041C362 74FA jz $0041c35e // je label1
0041C364 7404 jz $0041c36a // je label2
0041C366 7412 jz $0041c37a // je label3
0041C368 B005 mov al,$05 // mov al, 5
0041C36A 3C06 cmp al,$06 //.align 4 label2:cmp al, 6
0041C36C 74F0 jz $0041c35e // je label1
0041C36E 74FA jz $0041c36a // je label2
0041C370 7408 jz $0041c37a // je label3
0041C372 B007 mov al,$07 // mov al, 7
0041C374 3C08 cmp al,$08 // cmp al, 8
0041C376 74E6 jz $0041c35e // je label1
0041C378 74F0 jz $0041c36a // je label2
0041C37A C3 ret // je label3
// label3:
返回条件跳转指令:如何在Delphi汇编程序中将短条件跳转与分支目标对齐与.align
进行协调?
我承认在SkyLake和更高版本等处理器上对齐分支目标的好处很小,我知道我可以不使用.align
- 它也会保存代码大小。但我想知道如何使用Delphi汇编程序生成align
的短跳转。这个问题在32位目标中也存在,不仅在64位目标中。
答案 0 :(得分:2)
除非您的汇编程序可以选择进行更好的分支位移优化(可能需要重复传递),否则您可能会运气不好。 (当然,您可以自己手动完成所有对齐,但每次更改时都必须重新进行对齐。)
或者您可以使用不同的汇编程序进行汇编。但正如我所料,这是非常不受欢迎的because you lose access to Delphi-specific stuff like object layout for things declared outside of the asm。 (感谢@Rudy的评论。)
您可以在Delphi汇编程序中编写一些函数,尽可能多地使用Delphi特定的东西。将关键循环部分写入另一个汇编程序,hexdump将其机器代码输出转储到您放在Delphi程序集中间的db
伪指令。
如果每个函数的开头至少与函数内的任何函数一致,那么这可能正常工作,但是你可能最终浪费指令或将常量放入寄存器以供NASM部分使用,这可能是比只是拥有更长的分支更糟糕。
只有位于.align之后的指令才能正确编码为2字节短
这不是很准确。第一个je label1
看起来不错,它位于.align
之前。
看起来在尚未评估的.align
指令中前进的任何分支都为rel32
留出空间,并且汇编程序永远不会返回并修复它。其他情况似乎都很好:.align
上的向后分支,以及不与.align
交叉的前向分支。
分支位移优化不是一个简单的问题,尤其是当存在.align
指令时。不过,这似乎是一个非常次优的实现。
相关:Why is the "start small" algorithm for branch displacement not optimal?了解有关汇编程序用于分支位移优化的算法的更多信息。即使是优秀的汇编程序也可能无法做出最佳选择,尤其是在有.align
指令时。