当前位置: 首页 > 知识库问答 >
问题:

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

宗弘扬
2023-03-14

更新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

共有1个答案

欧阳翔
2023-03-14

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

Marco Cantü,Delphi开发人员版主

为什么Delphi zlib和zip库在64位以下如此缓慢?因为Win64库是在没有优化的情况下构建的。

在QP报告:编译器产生的RSP-9922坏ARM代码,$O指令忽略?中,Marco补充了如下解释:

这里有多个问题:

  • 如所示,优化设置仅应用于整个单元文件,而不应用于单个函数。简单地说,在同一个文件中打开和关闭优化不会有任何效果。
  • 此外,仅启用“调试信息”就会关闭优化。因此,在进行调试时,显式地打开优化将不会有任何效果。因此,IDE中的CPU视图将无法显示优化代码的反汇编视图。
  • 第三,加载未对齐的64位数据是不安全的,并且会导致错误,因此在给定的情况下需要单独进行4个1字节的操作。
 类似资料:
  • 我编写了所需的带有注释的接口和作为装饰器的抽象类。生成(mvn清理包)后,通过“默认”过程更新修饰的函数,得到的是参数和结果类型。我不知道,有什么问题。你能帮帮我吗? 环境:mapstruct版本1.4.2。lombok最终版本1.18.22(Spring boot 2.6.3)lombok mapstruct绑定:0.2.0 和 Mapper接口声明: 装饰师: } 以及生成的源:

  • 实际上,下面的代码不能用这个命令用Clang编译: . 我只想模仿与C中的“交换习惯用法”相同的行为,使用“using directive”来启用ADL。但是下面的代码哪里错了呢?预期的调用优先级应为:N1::foo 错误消息: 更新: 我将N2::foo更改为可以在某种程度上模仿std::交换的模板方法。所以,这里的问题是为什么不能由在函数中?因为当它们具有相同的优先级时,该函数应该比要调用的模

  • 问题内容: 在Java项目中,我正在使用第三方库,该第三方库通过 我希望能够从我的应用程序中影响此方法的搜索路径,以便用户无需在命令行上指定正确的java.library.path值(此值取决于当前操作系统)和建筑)。例如在Windows上,我想将其设置为“ lib / native / windows”,在Linux 32bit上,将其设置为“ lib / native / linux32”等。

  • 问题内容: 说,我有以下mixin通过触摸彼此重叠: 如果我希望我的视图通过该命令,请检查A->检查B,我的代码应该是还是? 为什么我们总是将其子类或子类放在mixins之后?(我通过阅读django通用视图的源代码注意到了这一点,但我不知道其背后的原理,如果有的话) 问题答案: MRO基本上是深度优先,从左到右。有关更多信息,请参见新型Python类中的方法解析顺序(MRO)。 你可以查看要检查

  • 我正在开发一个带有原生代码的Android库。此库项目将作为库添加到应用程序项目中。 我想在应用程序项目使用它时调试库。 我很确定库中的设置(makefile,构建命令,编译器选项)是可以的,因为我从库中尝试了调试器(我创建了一个虚拟活动并取消设置“库”),它的工作原理。我也意识到由于加载动态库所需的时间而可能发生的延迟,并且出于同样的原因,我认为这不是问题所在。 在应用程序项目中,我刚刚添加了本

  • 让我们用一个简单的C代码来设置寄存器: 当我使用1级优化为ARM(ARM none eabi gcc)编译此代码时,汇编代码如下所示: 看起来地址111111被解析到最接近的4K边界(110592)并移动到r3,然后通过将519添加到110592 (=111111)来存储值4096(0x1000)。为什么会这样? 在x86中,组装非常简单: