晚上好 . 对不起,我使用了google tradutor . 我在x86上的VC中使用NASM,我正在学习如何在x64上使用MASM .
是否有任何方法可以指定每个参数的位置以及汇编函数的返回方式,使编译器能够以最快的方式将数据保留在那里?我们也可以指定使用哪些寄存器,以便编译器知道哪些数据仍然保存以充分利用它?
例如,由于没有内部函数可以应用精确的IDIV r / m64(汇编语言的64位有符号整数除法),我们可能需要实现它 . IDIV要求被除数/分子的低幅度部分在RAX中,RDX中的高值和任何寄存器或存储器区域中的除数/分母 . 最后,商在EAX中,其余在EDX中 . 因此,我们可能希望开发函数(我将实际情况举例说明):
void DivLongLongInt( long long NumLow , long long NumHigh , long long Den , long long *Quo , long long *Rem ){
__asm(
// Specify used register: [rax], specify pre location: NumLow --> [rax]
reg(rax)=NumLow ,
// Specify used register: [rdx], specify pre location: NumHigh --> [rdx]
reg(rdx)=NumHigh ,
// Specify required memory: memory64bits [den], specify pre location: Den --> [den]
mem[64](den)=Den ,
// Specify used register: [st0], specify pre location: Const(12.5) --> [st0]
reg(st0)=25*0.5 ,
// Specify used register: [bh]
reg(bh) ,
// Specify required memory: memory64bits [nothing]
mem[64](nothing) ,
// Specify used register: [st1]
reg(st1)
){
// Specify code
IDIV [den]
}(
// Specify pos location: [rax] --> *Quo
*Quo=reg(rax) ,
// Specify pos location: [rdx] --> *Rem
*Rem=reg(rdx)
) ;
}
是否有可能做到至少接近于此的事情?谢谢你的帮助 .
如果没有办法做到这一点,那将是一种耻辱,因为它肯定是用汇编级功能实现高级函数的好方法 . 我认为它应该是C和ASM之间的一个简单接口,它应该已经存在并使汇编代码能够内嵌和高级嵌入,实际上就像简单的C代码一样 .
2 回答
As others have mentioned,MSVC在定位x86-64时不支持任何形式的内联汇编 .
内联汇编仅在x86-32版本中受支持,即使在那里,它的功能也相当有限 . 特别是,您不能指定输入和输出,因此内联汇编的使用必然需要在寄存器和内存之间来回传递大量值,这恰恰与编写高性能代码时的需求相反 . 除非通过手动发出机器代码,否则除了通过手动发出机器代码之外你不可能做任何其他事情,你应该避免使用内联汇编程序 . 它最初的目的是在过时的8位和16位编程环境中执行生成
OUT
指令和调用ROM BIOS中断等操作 . 为了兼容性目的,它使它成为32位编译器,但团队使用了64位 .Intrinsics现在是推荐的解决方案,因为这些优化器可以更好地发挥作用 . 实际上,您需要编译器生成的任何SIMD代码都可以使用内在函数来完成,就像大多数其他针对x86的编译器一样,因此您不仅可以获得更好的代码,而且还可以获得稍微更轻松的代码 .
即使在支持extended asm blocks的Gnu风格的编译器上,它们为您提供了所需的输入/输出操作数功能,但仍然有lots of good reasons to avoid the use of inline asm . Intrinsics仍然是一个更好的解决方案,就像找到一种方法来表示你想要的C并说服编译器生成你希望它发出的汇编代码 .
唯一的例外是没有内在函数的情况 . 不幸的是,
IDIV
指令是其中一种情况 . (有内在函数可用于128位乘法 . 它们有各种名称:Windows-specific或compiler-specific . )在支持128位整数类型作为64位目标扩展的Gnu编译器上,您可以让编译器为您生成代码:
现在,这通常被编译为对其函数执行128位除法的调用,而不是返回64位商的内联
IDIV
指令 . 据推测,这是因为需要处理溢出,as David mentioned . 实际上,它比那更糟糕 . 没有C或C实现可以使用DIV
/IDIV
指令,因为它们不符合要求 . 这些指令将导致溢出异常,而标准表示结果应该被截断 . (使用乘法,你会得到内联IMUL
/MUL
指令,因为它们没有溢出问题,因为它们返回128位结果 . )这实际上并不像你想象的那么大 . 您似乎假设64位
IDIV
指令非常快 . 事实并非如此 . 虽然实际数字取决于被除数的绝对值中的有效位数,但如果您确实需要128位整数的范围,则您的值可能非常大 . 查看Agner Fog's instruction tables将了解您可以在各种体系结构上获得的性能 . 它虽然英特尔仍然表现不佳,但它仍有相当大的延迟 . 仅仅因为它意味着它在一个周期或类似的东西中运行 . 当你重要的时候,单个指令可能对代码密度有益 . 实际上,除法很慢,以至于编译器非常努力地不使用它 - 只要有可能,它们就会乘以倒数,这会快得多 . 如果你真的需要快速进行乘法运算,你应该考虑使用SIMD指令并行化,这些指令都有内在函数可用 .回到MSVC(虽然我在上一段中所说的一切仍然适用,当然),没有128位整数类型,所以如果你需要实现这种类型的除法,你需要在外部程序集中编写代码模块并将其链接进去 . 代码非常简单,Visual Studio具有出色的内置支持,可以使用MASM汇编代码并将其直接链接到项目中:
然后你只需在C代码中将其原型化为:
你完成了根据需要调用它 .
可以使用
DIV
指令为无符号除法写入等效项 .不,你在前端的寄存器重命名真的很重要,通常可以完全忽略寄存器寄存器的移动(换句话说,
MOV
成为零延迟操作) . 另外,IDIV
指令在其操作数方面是如此限制,因为它们被硬编码为RAX
和RDX
,调度程序不太可能将这些值保存在这些寄存器中,至少对于任何非平凡的部分而言都是如此 . 代码请注意,一旦你编写了必要的代码来检查溢出的可能性,或者更坏 - 处理异常的代码 - 这很可能最终会执行与执行正确128位除法的库函数相同或更差,所以你可以说只是编写和使用它(直到微软认为适合提供一个) . 这可以是written in C(也可以参见Gnu编译器的
__divti3
库函数的实现),这使得它成为内联的候选者,并且与优化器一起玩得更好 .不,这是不可能的 . MSVC不支持x64版本的内联汇编 . 相反,你应该使用内在函数; almost 一切都可以 . 可悲的是,据我所知,内在函数中缺少128位
idiv
.注意:您可以使用两个
mov
来解决您的问题(将输入放在正确的寄存器中) . 而你不应该担心;当前CPU处理mov
very . 将mov
放入代码可能不会减慢速度 .div
与mov
相比非常昂贵,所以它并不重要 .