如何影响delphiXEx代码生成的Android / ARM目标?

更新2017-05-17。 我不再为发生此问题的公司工作,也无法访问Delphi XEx。 当我在那里的时候,问题通过迁移到混合FPC + GCC(Pascal + C)来解决,NEON内在函数对于一些例程有所不同。 (强烈build议使用FPC + GCC,因为它可以使用标准工具,尤其是Valgrind。)如果有人能够用可靠的例子演示如何实际上能够从Delphi XEx生成优化的ARM代码,我很乐意接受答案。


Embarcadero的Delphi编译器使用LLVM后端为Android设备生成原生ARM代码。 我有大量的Pascal代码,我需要编译到Android应用程序,我想知道如何使delphi生成更有效的代码。 现在,我甚至没有谈到像自动SIMD优化这样的高级function,而是谈论生成合理的代码。 当然,必须有办法将parameter passing给LLVM端,或者以某种方式影响结果? 通常情况下,任何编译器都会有很多选项来影响代码的编译和优化,但是Delphi的ARM目标似乎只是“优化开/关”,就是这样。

LLVM应该能够产生相当严密和合理的代码,但是似乎Delphi正在以一种奇怪的方式使用它的设施。 Delphi希望非常重要地使用堆栈,并且通常只使用处理器的寄存器r0-r3作为临时variables。 也许最疯狂的是,它似乎是加载正常的32位整数作为四个1字节的加载操作。 如何使delphi产生更好的ARM代码,并没有逐字节的麻烦,它为Android?

起初我以为逐字节加载是为了交换大字节的字节顺序,但事实并非如此,它实际上只是加载了一个32位的数字,有4个单字节的加载。*可能是加载完整的32位没有做一个不alignment的字大小的内存负载。 (是否应该避免这是另一回事,这将暗示整个事情是一个编译器错误)*

让我们看看这个简单的function:

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位整数…如果我改变了一点点的function,并使用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(2014年3月的SVN中继版本)的优化级别-O2的function。 指针和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编译器testing了一个等效的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 

我们正在调查这个问题。 简而言之,它取决于由指针引用的Integer的潜在错误alignment(到32边界)。 需要多一点时间才能得到所有的答案,并制定计划来解决这个问题。

Delphi开发人员主持人MarcoCantù

另外引用为什么在64位下Delphi的zlib和zip库很慢? 因为Win64库是在没有优化的情况下生成的。


在QP报告中: 编译器生成的RSP-9922错误的ARM代码,忽略了$ O指令? ,Marco补充说明如下:

这里有很多问题:

  • 如上所述,优化设置仅适用于整个单元文件,而不适用于单个function。 简而言之,在同一个文件中打开和closures优化将不起作用。
  • 而且,只需启用“debugging信息”即可closures优化。 因此,在debugging时,明确地打开优化将不起作用。 因此,IDE中的CPU视图将无法显示优化代码的反汇编视图。
  • 第三,加载未alignment的64位数据是不安全的,并且会导致错误,因此在给定场景中需要单独的4个一字节操作。