更新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以一种奇怪的方式使用它的设施.Delphi希望非常频繁地使用堆栈,它通常只利用处理器的寄存器r0-r3作为临时变量.也许是最疯狂的,似乎是将正常的32位整数加载为四个1字节的加载操作.如何让Delphi产生更好的ARM代码,而且没有逐字节麻烦的Android?
起初我认为逐字节加载是用于从big-endian交换字节顺序,但事实并非如此,它实际上只是加载一个带有4个单字节加载的32位数字.*可能是加载完整的32位而不进行未对齐的字大小的内存加载.(是否应该避免这是另一回事,这将暗示整个事情是编译器错误)*
让我们来看看这个简单的函数:
function ReadInteger(APInteger : PInteger) : Integer;
begin
Result := APInteger^;
end;
Run Code Online (Sandbox Code Playgroud)
即使启用了优化,带有更新包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] …
Run Code Online (Sandbox Code Playgroud) Embarcadero的docwiki页面基于LLVM的Delphi编译器列出了Delphi XE8中的几种语言更改.其中一颗子弹说:
基于LLVM的Delphi编译器不支持使用指针.
这在实践中究竟意味着什么?以前在Delphi XE7中使用哪些与指针相关的东西,在Delphi XE8中不再有效?我似乎无法在Embarcadero的网站上找到对此的深入解释.例如,据说包含更多信息的从桌面迁移Delphi代码到Mobile的页面没有提到"Pointer"这个词.
将默认值(TMyRecord)分配给TMyRecord的变量称为内部调用Finalize,然后将内存清零,就像FillChar那样.例如,在以下问题的答案中已经说过,我确实测试过分配Default()确实会导致调用例如System._FinalizeRecord
Initialize(),Default()和FillChar()之间的区别
我的问题是,即使在Delphi没有自动调用Initialize的情况下,初始化这样的记录是否总是安全的?对我来说,在未初始化的记录变量上调用Finalize似乎没有意义.在初始化之前,必须假定内存包含随机垃圾.在这种情况下,我对托管类型特别感兴趣,这些托管类型是动态分配内存的指针,Finalize例程应该通过减少它们的引用计数来完成,等等.在许多情况下,Delphi会自动生成对Initialize的调用,以确保其托管类型保持可管理状态.但不总是.
这是一个示例,说明了一个有问题的案例.正如下面的答案所述,你不应该使用GetMem来分配包含这样的托管类型的记录,但是让我们假设某人做了,然后尝试使用Default()赋值作为初始化
type
TMyRecord = record
s1, s2, s3 : String;
end;
PMyRecord = ^TMyRecord;
var
pr : PMyRecord;
begin
GetMem(pr, SizeOf(TMyRecord));
pr^ := Default(TMyRecord);
...
Run Code Online (Sandbox Code Playgroud)
我故意使用GetMem()而不是New(),因为据我所知,GetMem()返回的内存不应该自动归零,并且编译器不应该自动调用Initialize.那么在这种情况下,使用默认分配初始化记录不是不安全吗?
在大卫接受的答案中,他正在使用一种漂亮的Clear方法记录类型 如何在Delphi中正确释放包含各种类型的记录? 让我们添加一个
TMyRecord = record
s1, s2, s3 : String;
procedure Clear;
end;
...
procedure TMyRecord.Clear;
begin
Self := Default(TMyRecord);
end;
Run Code Online (Sandbox Code Playgroud)
现在,Clear例程应该完全无法知道记录是否位于堆栈或堆上,并且是否已在其上调用Initialize.