更新2017-05-17。我不再为这个问题产生的公司工作,也没有访问Delphi XEX的权限。当我在那里的时候,这个问题通过迁移到混合的FPC+GCC(Pascal+C)得到了解决,在一些例程中使用了霓虹灯的固有特性,在那里它起到了很大的作用。(强烈推荐使用FPC+GCC,因为它支持使用标准工具,特别是Valgrind。)如果有人能用可信的例子来演示他们是如何从Delphi XEx中生成优化的ARM代码的,我很乐意接受这个答案。
Enbarcadero的Delphi编译器使用LLVM后端为Android设备生成本机ARM代码。我有大量的Pascal代码需要编译到Android应用程序中,我想知道如何让Delphi生成更高效的代码。现在,我甚至不是在谈论自动SIMD优化之类的高级特性,而是在谈论生成合理的代码。肯定有一种方法可以将参数传递给LLVM端,或者以某种方式影响结果?通常,任何编译器都会有很多选项来影响代码编译和优化,但Delphi的ARM目标似乎只是“优化开/关”,仅此而已。
LLVM应该能够生成合理的紧凑和合理的代码,但Delphi似乎在以一种奇怪的方式使用它的工具。Delphi非常希望使用堆栈,它通常只利用处理器的寄存器r0-r3作为临时变量。可能是最疯狂的,它似乎是加载正常的32位整数作为四个1字节的加载操作。如何让Delphi生成更好的ARM代码,而不是像Android那样一个字节一个字节的麻烦?
一开始我以为逐字节加载是为了交换big-endian的字节顺序,但事实并非如此,它实际上只是加载一个32位的数字,加载4个单字节。*它可能是加载完整的32位,而不进行未对齐的字大小的内存加载。(它是否应该避免这是另一回事,这将暗示整个事情是一个编译器bug)*
我们来看看这个简单的函数:
function ReadInteger(APInteger : PInteger) : Integer;
begin
Result := APInteger^;
end;
即使启用了优化,带更新包1的Delphi XE7以及XE6也会为该函数生成以下ARM汇编代码:
Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:
00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
0: b580 push {r7, lr}
2: 466f mov r7, sp
4: b083 sub sp, #12
6: 9002 str r0, [sp, #8]
8: 78c1 ldrb r1, [r0, #3]
a: 7882 ldrb r2, [r0, #2]
c: ea42 2101 orr.w r1, r2, r1, lsl #8
10: 7842 ldrb r2, [r0, #1]
12: 7803 ldrb r3, [r0, #0]
14: ea43 2202 orr.w r2, r3, r2, lsl #8
18: ea42 4101 orr.w r1, r2, r1, lsl #16
1c: 9101 str r1, [sp, #4]
1e: 9000 str r0, [sp, #0]
20: 4608 mov r0, r1
22: b003 add sp, #12
24: bd80 pop {r7, pc}
只需计算Delphi为此所需的指令和内存访问数。并通过4个单字节加载构造一个32位整数...如果我稍微改变一下函数,使用一个var参数而不是指针,它就稍微不那么复杂了:
Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:
00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
0: b580 push {r7, lr}
2: 466f mov r7, sp
4: b083 sub sp, #12
6: 9002 str r0, [sp, #8]
8: 6801 ldr r1, [r0, #0]
a: 9101 str r1, [sp, #4]
c: 9000 str r0, [sp, #0]
e: 4608 mov r0, r1
10: b003 add sp, #12
12: bd80 pop {r7, pc}
在这里我就不包括拆解了,但是对于iOS,Delphi对于指针和var参数版本产生了完全相同的代码,而且它们与Android var参数版本几乎但不完全相同。编辑:澄清一下,逐个字节的加载只在Android上进行。并且只有在Android上,指针和var参数版本彼此不同。在iOS上,这两个版本生成的代码完全相同。
作为对比,下面是FPC 2.7.1(SVN主干版从2014年3月开始)对优化级别-O2的功能的看法。指针和var参数版本完全相同。
Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:
00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:
0: 6800 ldr r0, [r0, #0]
2: 46f7 mov pc, lr
我还用Android NDK附带的C编译器测试了一个等价的C函数。
int ReadInteger(int *APInteger)
{
return *APInteger;
}
这将编译成FPC制作的基本相同的东西:
Disassembly of section .text._Z11ReadIntegerPi:
00000000 <_Z11ReadIntegerPi>:
0: 6800 ldr r0, [r0, #0]
2: 4770 bx lr
我们正在调查这个问题。简而言之,它取决于指针引用的整数的潜在错对(到32边界)。需要更多的时间来找到所有的答案...以及解决这一问题的计划。
Marco Cantü,Delphi开发人员版主
为什么Delphi zlib和zip库在64位以下如此缓慢?因为Win64库是在没有优化的情况下构建的。
在QP报告:编译器产生的RSP-9922坏ARM代码,$O指令忽略?中,Marco补充了如下解释:
这里有多个问题: