如何影响Delphi XE的代码生成以针对Android/ARM目标?

270

更新于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平台上进行字节级操作?

起初,我认为字节级加载是用于从大端交换字节顺序,但事实并非如此,它实际上只是进行了4次单字节加载以加载32位数字。可能是为了加载完整的32位而不进行未对齐的字大小内存加载。(是否应该避免这样做是另一回事,这暗示了整个编译器存在错误)*

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

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;
即使开启了优化,在Delphi XE7和XE6中,使用更新包1编译的代码仍会生成以下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

15
顺便提一下,在[Google+讨论](https://plus.google.com/116741183961858352601/posts/aw3Beve3nAu)中,Sam Shaw指出C ++在调试版本中显示长格式代码,在发布版本中显示优化代码,而Delphi则两者都会显示。因此,可能是他们向LLVM发送的标志存在简单错误,如果是这样,提交一个错误报告非常值得,它可能很快就会得到修复。 - David
9
好的,我误读了。那么,像Notlikethat所说的那样,它似乎假定指针加载是未对齐的(或不能保证对齐),而旧的ARM平台不一定能够执行未对齐的加载操作。请确保您将其构建为armeabi-v7a而不是armeabi(不确定此编译器是否提供此类选项),因为自ARMv6以来应支持未对齐的加载(而armeabi则假定ARMv5)。 (所显示的反汇编看起来并不像在读取大端值,而只是一次一个字节地读取小端值。) - mstorsjo
6
我找到了RSP-9922,看起来是同一个 bug。 - David
6
在embarcadero.public.delphi.platformspecific.ios新闻组中,有人问关于XE4和XE5之间优化是否被破坏的问题,“ARM编译器优化是否失效?”http://www.devsuperpage.com/search/Articles.aspx?hl=en&G=2&ArtID=141696 - Side S. Fresh
6
@Johan:这是什么可执行文件?我有印象它似乎被内嵌在Delphi编译器的可执行文件中。试一试并告诉我们结果。 - Side S. Fresh
显示剩余9条评论
1个回答

11
我们正在调查此问题。简而言之,这取决于指针引用的整数是否与32边界对齐不当。需要一些时间来获得所有答案...并制定解决方案。 ——Marco Cantù,Delphi Developers的主持人。
此外,请参考为什么Delphi的zlib和zip库在64位下如此缓慢?,因为Win64库是没有进行优化构建的。
在QP报告中:RSP-9922编译器生成错误的ARM代码,$O指令被忽略?,Marco添加了以下解释:
“这里有多个问题:
  • 正如所示,优化设置仅适用于整个单元文件,而不适用于单个函数。简而言之,在同一文件中打开和关闭优化将没有任何效果。
  • 此外,仅启用“调试信息”就会关闭优化。因此,在进行调试时,明确打开优化将没有任何效果。因此,IDE中的CPU视图将无法显示优化代码的反汇编视图。
  • 第三,加载非对齐的64位数据是不安全的,并且会导致错误,因此在特定情况下需要进行分离的4个单字节操作。

Marco Cantù在2015年1月发布了一条消息:“我们正在调查这个问题”,相关的错误报告RSP-9922在2016年1月被标记为“按预期工作”解决,还提到“内部问题于2015年3月2日关闭”。我不理解他们的解释。 - Side S. Fresh
3
我在问题解决方案中添加了一条评论。 - Marco Cantù

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接