x64代码中的对齐问题,Free Pascal

3

如果使用适当的寄存器重命名对32位进行编译,则下面的代码可以正常工作。但是如果执行时会出现错误(并在编译时显示“警告:对象文件“project1.o”包含32位绝对重定位到符号“.data.n_tc_p $ project1_orbitkeyheader64 $ int64 $ longint $$ int64_shufidx”。)

function SwapBytes64(const Val: Int64): Int64;
{$A 16}
const
  SHUFIDX : array [0..1] of Int64 = ($0001020304050607, 0);
begin
asm
  movq          xmm0, rcx
  pshufb        xmm0, SHUFIDX    // throws
  movq          rax, xmm0
end;
end;

我该如何纠正这个问题(最好能够对齐常量)。

编辑 我还尝试了使用movdqu。

回答 这是@Jester答案的结果:

function SwapBytes64(const Val: Int64): Int64;
const
  SHUFIDX : array [0..1] of Int64 = ($0001020304050607, 0);
begin
asm
  movq          xmm0, rcx
  movdqu        xmm1, [rip+SHUFIDX]
  pshufb        xmm0, xmm1
  movq          rax, xmm0
end;
end;

这样也有效果,但似乎没有明显的速度优势:
function SwapBytes64(const Val: Int64): Int64;
const
  SHUFIDX : array [0..1] of Int64 = ($0001020304050607, 0);
begin
asm
  movq          xmm0, rcx
  pshufb        xmm0, [rip+SHUFIDX]
  movq          rax, xmm0
end;
end;

既然它是一个本地变量,难道不应该像 [rbp-8] 这样来寻址本地变量吗? - wilx
64位模式不支持该常量,而32位模式可以。这是一个定义对齐问题。我不需要像rbp-8这样的东西,因为我可以直接引用该常量。 - IamIC
什么平台?我记得在Windows上看到类似的错误,当符号实际上没有定义在任何地方时。 - wilx
我正在使用Lazarus / Free Pascal编译,目标是Win64 / Athlon64。Lazarus在32位虚拟机中,并且我正在从C#中调用代码。在32位模式下,我可以在本地测试它,并且它可以正常工作(也没有编译器警告)。 - IamIC
请参见https://dev59.com/9GIk5IYBdhLWcg3wl_LE,其中涉及到x64汇编中的堆栈对齐问题。 - Jay
Jay,我不确定如何将其应用于常量。 - IamIC
3个回答

5
可能并不是对齐的问题。编译器已经警告您,对SHUFIDX的绝对引用将被截断为32位。如果地址不在前4GiB内,则会导致错误的内存引用。您应该在调试器中检查此问题。
作为解决方法,您应该使用rip相对或间接寻址。前者可能类似于movdqu xmm1, [rip+SHUFIDX]movdqu xmm1, rel SHUFIDX或类似的内容。请参阅您的编译器手册。

2
@IanC 为了让其他人受益,您能告诉我们编译器接受了哪种语法吗? - Jester
根据@TheRaven的回答,应该是[RIP + var]。哦,原帖也把答案编辑到问题里了。 :/ - Peter Cordes

3

与您实际问题无关,您的代码不安全。除非您编写一个纯汇编语言函数("assembler; asm .. end;",或—在 Delphi 模式下—只包含一个没有周围 "begin .. end;" 的 "asm .. end;" 语句块),否则编译器会在您的汇编块之前和之后插入代码。特别地,在您的汇编块执行完成后,它可能会覆盖 rax 的值。

要解决这个问题,要么使您的函数成为纯汇编函数,要么在末尾添加 "movq @result, rax"。


请记住,这种类型的评论应该在评论部分进行,只有相关的答案才应该在此发布。 - vcanales

1

RIP + 变量名 解决了我的问题,因为所涉及的变量被截断为32位内存分配。我甚至将变量空间明确为Int64也没有成功。将RAX加载一个值,然后将其分配给变量可以工作,但需要额外编码来使32位代码块大小加倍。

MOV qword[var], RBX会报错

这个方法可以解决问题,但会使代码臃肿:

MOV RAX, RBX
MOV qword[var], RAX

虽然这在使用较少的MOV指令时可以正常工作:

MOV qword[RIP + var], RBX

很抱歉,它可以在FAsm和NAsm上运行,但是64位输出的FreePascal内联汇编器会因为64位可执行文件到32位的内存空间截断而引发寻址错误-编译器错误。感谢您的支持 - 是的,当内存分配明确设置为四个字时,将RBX移动到var应该绝对有效,我同意Peter Cordes的观点。保重。 - user7782863
正如所述,FreePascal编译器(本讨论的主题)正在截断内存(从64位到32位),FPC似乎只尊重RAX这个通用寄存器(就内存大小而言);还有其他寄存器,但仍然很麻烦。因此,我使用rip + 内存地址(通过命名引用)来进行补偿。这是一个RIP问题,并且在FPC中已知 - 我仍然不明白为什么这个问题没有被解决,因为这个问题已经存在了2年。 - user7782863
我还想知道为什么编译器在64位系统中将内存分配截断为32位,而默认的malloc大小应该是64位,除非明确指定为32位,这似乎是FPC中的逻辑错误(设计),从而导致了本主题讨论的实际错误。 - user7782863
1
哦,我刚意识到为什么这个可以工作。因为rax可以使用64位绝对地址mov moffs8/16/32/64编码,这只适用于al/ax/eax/rax。(AT&T语法称之为movabs)。所以从RAX存储可以使用64位绝对地址,但是从RBX存储则必须截断地址。你应该总是使用RIP相对寻址来寻址静态数据,因为它更短。我想FPC拒绝使用32位绝对地址,即使对于静态数据(即使在64位中)也可以,除非你正在制作PIC代码。 - Peter Cordes
你是说malloc只在虚拟地址空间的低32位中分配内存,所以可以将指针视为32位吗? 这很奇怪(与寻址静态数据无关)。Linux的x32 ABI就是这样做的;长模式下的32位指针。但当然静态地址仅为32位。 - Peter Cordes
显示剩余4条评论

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