【问题标题】:How to affect Delphi XEx code generation for Android/ARM targets?如何影响 Android/ARM 目标的 Delphi XEx 代码生成?
【发布时间】:2015-03-12 20:09:42
【问题描述】:

2017 年 5 月 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 似乎以一种奇怪的方式使用它的设施。 Delphi 非常想使用堆栈,它通常只使用处理器的寄存器 r0-r3 作为临时变量。也许最疯狂的是,它似乎将普通的 32 位整数加载为四个 1 字节的加载操作。如何让 Delphi 产生更好的 ARM 代码,并且没有它为 Android 带来的逐字节的麻烦?

起初我以为逐字节加载是为了从大端交换字节顺序,但事实并非如此,它实际上只是加载一个 32 位数字和 4 个单字节加载。* 它可能是加载完整的 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

【问题讨论】:

  • 顺便说一句,Google+ discussion 关于这一点,Sam Shaw 指出 C++ 在调试版本中显示了长格式代码,在发布中显示了优化代码。德尔福在哪里都做到了。因此,这很可能是他们发送 LLVM 的标志中的一个简单错误,如果是这样一个错误报告非常值得提交,它可能很快就会得到修复。
  • 哦,好吧,我看错了。然后,正如 Notlikethat 所说,听起来它假设指针加载将是未对齐的(或不能保证对齐),并且较旧的 ARM 平台不一定可以进行未对齐的加载。确保它的构建目标是 armeabi-v7a 而不是 armeabi(不确定此编译器中是否有这样的选项),因为自 ARMv6 起应支持未对齐的加载(而 armeabi 假定为 ARMv5)。 (显示的反汇编看起来不像读取大端值,它只是一次读取一个字节的小端值。)
  • 我发现RSP-9922 似乎是同样的错误。
  • 在 embarcadero.public.delphi.platformspecific.ios 新闻组中,有人询问 XE4 和 XE5 之间的优化是否被破坏,“ARM 编译器优化被破坏?” devsuperpage.com/search/…
  • @Johan:它是什么可执行文件?我的印象是它以某种方式在 Delphi 的编译器可执行文件中烘焙。试一试,让我们知道结果。

标签: android delphi android-ndk arm llvm


【解决方案1】:

我们正在调查此问题。简而言之,它取决于指针引用的 Integer 的潜在未对齐(到 32 边界)。需要更多时间来获得所有答案......以及解决这个问题的计划。

Marco Cantù,Delphi Developers 的版主

还可以参考Why are the Delphi zlib and zip libraries so slow under 64 bit?,因为 Win64 库是在没有优化的情况下构建的。


在 QP 报告中:RSP-9922 Bad ARM code produced by the compiler, $O directive ignored?,Marco 添加了以下解释:

这里有多个问题:

  • 如前所述,优化设置仅适用于整个单元文件,而不适用于单个函数。简单来说,在同一个文件中开启和关闭优化是没有效果的。
  • 此外,只需启用“调试信息”即可关闭优化。因此,在调试时,显式打开优化将无效。因此,IDE 中的 CPU 视图将无法显示优化代码的反汇编视图。
  • 第三,加载未对齐的 64 位数据是不安全的,并且会导致错误,因此在给定场景中需要单独的 4 个单字节操作。

【讨论】:

  • Marco Cantù 在 2015 年 1 月发布了“我们正在调查该问题”的说明,相关的错误报告 RSP-9922 在 2016 年 1 月被标记为“按预期工作”的解决方案,并且有提及“内部问题已于 2015 年 3 月 2 日结束”。我不明白他们的解释。
  • 我在问题解决方案中添加了评论。
猜你喜欢
  • 2019-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多