Assembly 向x86-64 ABI的指针添加32位偏移量时,是否需要符号或零扩展名?
小结:我正在查看汇编代码来指导我的优化,在将int32添加到指针时,我看到了很多符号或零扩展Assembly 向x86-64 ABI的指针添加32位偏移量时,是否需要符号或零扩展名?,assembly,x86-64,compiler-optimization,abi,sign-extension,Assembly,X86 64,Compiler Optimization,Abi,Sign Extension,小结:我正在查看汇编代码来指导我的优化,在将int32添加到指针时,我看到了很多符号或零扩展 void Test(int *out, int offset) { out[offset] = 1; } ------------------------------------- movslq %esi, %rsi movl $1, (%rdi,%rsi,4) ret 起初,我认为我的编译器在将32位整数添加到64位整数时遇到了挑战,但我已经用Intel ICC 11、ICC 14和
void Test(int *out, int offset)
{
out[offset] = 1;
}
-------------------------------------
movslq %esi, %rsi
movl $1, (%rdi,%rsi,4)
ret
起初,我认为我的编译器在将32位整数添加到64位整数时遇到了挑战,但我已经用Intel ICC 11、ICC 14和GCC 5.3证实了这种行为
这证实了我的发现,但不清楚是否需要符号或零扩展。只有在尚未设置高位32位时,才需要此符号/零扩展。但是x86-64 ABI是否足够聪明,能够满足这一要求
我有点不愿意将所有指针偏移量更改为ssize\t,因为寄存器溢出会增加代码的缓存占用空间。因为EOF的注释表明编译器不能假设用于传递32位参数的64位寄存器的上32位具有任何特定值。这就需要符号或零扩展 防止这种情况发生的唯一方法是为参数使用64位类型,但这将要求将值扩展到调用方,这可能不是改进。不过,我不会太担心寄存器溢出的大小,因为现在这样做很可能在扩展后原始值将失效,而溢出的是64位扩展值。即使它没有死,编译器也可能更愿意溢出64位值
如果您真正关心内存占用,并且不需要更大的64位地址空间,您可以查看使用ILP32类型但支持完整64位指令集的 是的,您必须假设arg或返回值寄存器的高32位包含垃圾。另一方面,你可以在打电话或回电话时把垃圾留在高32。i、 e.责任在于接收侧忽略高位,而不是通过侧清除高位 您需要对64位进行签名或零扩展以使用64位有效地址中的值。在中,gcc经常使用32位有效地址,而不是对修改用作数组索引的潜在负整数的每条指令使用64位操作数大小
标准: 只说明寄存器的哪些部分为
\u Bool
(又称Bool
)的零。第20页:
在寄存器或on中返回或传递类型为\u Bool
的值时
堆栈中,位0包含真值,位1至7应为
零(脚注14:其他位未指定,因此这些值的使用者端在截断为8位时可以依赖于0或1)
另外,关于%al
为varargs函数保存FP寄存器arg数的内容,而不是整个%rax
关于这个确切的问题,我有一个疑问
ABI没有对整型寄存器或向量寄存器中包含参数或返回值的高位部分的内容提出任何进一步的要求或保证,因此没有任何要求或保证。我通过Michael Matz(ABI维护人员之一)的电子邮件确认了这一事实:“一般来说,如果ABI没有说指定了什么,你就不能依赖它。”
他还证实了,例如(这提醒我应该报告)。他补充说,这曾经是AMD实现glibc数学函数时的一个问题。当传递标量double
或float
args时,普通C代码会在向量regs的高位元素中留下垃圾
标准中未(尚未)记录的实际行为: 窄函数参数,即使是
\u Bool
/Bool
,都是扩展到32位的符号或零。clang甚至会生成依赖于这种行为的代码。ICC17,因此ICC和clang不兼容ABI,即使对于C也是如此。如果前6个整数参数中的任何一个小于32位,请不要从x86-64 SysV ABI的ICC编译代码调用clang编译函数
这不适用于返回值,只有args:gcc和clang都假设它们接收的返回值的有效数据不超过类型的宽度。例如,gcc将使返回char的函数在%eax
的高24位留下垃圾
一个建议是澄清将8位和16位arg扩展到32位的规则,并可能实际修改ABI以满足这一要求。主要的编译器(ICC除外)已经这样做了,但这将改变调用方和被调用方之间的契约
下面是一个示例(请与其他编译器一起查看或调整代码,其中我包含了许多简单示例,这些示例仅演示了拼图的一部分,也演示了很多):
注:movzwl数组_us(,%rax,2)
将是等效的,但不会更小。如果我们可以依赖于%rax
的高位在fuint()
的返回值中归零,那么编译器可以使用array\u us(%rbx,%rax,2)
而不是使用add
insn
性能影响 不定义high32是故意的,我认为这是一个很好的设计决策 在执行32位运算时,忽略高32是自由的,因此,如果您可以在64位寻址模式或64位操作中直接使用reg,则只需要额外的
mov edx、edi
或其他功能
有些函数无法避免INSN将其参数扩展到64位,因此调用方总是必须这样做是一种潜在的浪费。有些函数使用参数的方式需要与参数的有符号性相反的扩展,因此让被调用方来决定如何更好地工作
然而,对于大多数调用者来说,零扩展到64位(不考虑签名性)是免费的,并且可能是ABI设计的一个不错的选择。因为arg reg是clo
extern short fshort(short a);
extern unsigned fuint(unsigned int a);
extern unsigned short array_us[];
unsigned short lookupu(unsigned short a) {
unsigned int a_int = a + 1234;
a_int += fshort(a); // NOTE: not the same calls as the signed lookup
return array_us[a + fuint(a_int)];
}
# clang-3.8 -O3 for x86-64. arg in %rdi. (Actually in %di, zero-extended to %edi by our caller)
lookupu(unsigned short):
pushq %rbx # save a call-preserved reg for out own use. (Also aligns the stack for another call)
movl %edi, %ebx # If we didn't assume our arg was already zero-extended, this would be a movzwl (aka movzx)
movswl %bx, %edi # sign-extend to call a function that takes signed short instead of unsigned short.
callq fshort(short)
cwtl # Don't trust the upper bits of the return value. (This is cdqe, Intel syntax. eax = sign_extend(ax))
leal 1234(%rbx,%rax), %edi # this is the point where we'd get a wrong answer if our arg wasn't zero-extended. gcc doesn't assume this, but clang does.
callq fuint(unsigned int)
addl %ebx, %eax # zero-extends eax to 64bits
movzwl array_us(%rax,%rax), %eax # This zero-extension (instead of just writing ax) is *not* for correctness, just for performance: avoid partial-register slowdowns if the caller reads eax
popq %rbx
retq