如何影响用于Android / ARM目标的Delphi XEx代码生成?

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


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

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

起初,我认为逐字节加载是为了交换来自big-endian的字节顺序,但事实并非如此,它实际上只是加载一个带有4个单字节加载的32位数字。*它可能是加载完整的32位而不会执行未对齐的字大小的内存加载。 (它是否应该避免这是另一回事,这会暗示整个事情是一个编译器错误)*

让我们看看这个简单的函数:

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(2014年3月的SVN中继版本)认为具有优化级别-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边界)。 需要多一点时间来获得所有答案......并制定计划来解决这个问题。

Delphi开发人员主持人MarcoCantù

另外引用为什么在64位下Delphi的zlib和zip库很慢? 因为Win64库的发货没有优化。


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

这里有很多问题:

  • 如上所述,优化设置仅适用于整个单元文件,而不适用于单个功能。 简而言之,在同一个文件中打开和关闭优化将不起作用。
  • 此外,只需启用“调试信息”即可关闭优化。 因此,当一个人正在调试时,明确地打开优化将不起作用。 因此,IDE中的CPU视图将无法显示优化代码的反汇编视图。
  • 第三,加载未对齐的64位数据是不安全的并且会导致错误,因此在给定场景中需要单独的4个一字节操作。
  • 链接地址: http://www.djcxy.com/p/34943.html

    上一篇: How to affect Delphi XEx code generation for Android/ARM targets?

    下一篇: Passing pointers to records