Knm*_*Knm 5 c assembly int128 cpu-registers
考虑下面的代码。我们知道一个__uint128_t变量存储在 2 个 64 位寄存器中(假设 x64 处理器)。要求是将前 64 位存储在一个 unsigned long 变量中,并将接下来的 64 位存储在另一个 unsigned long 变量中。
__uint128_t a = SOMEVALUE;
unsigned long b = a&0xffffffffffffffff;
unsigned long c = a>>64;
Run Code Online (Sandbox Code Playgroud)
这里,b存储前 64 位,c存储接下来的 64 位。有没有其他更简单的方法来分别访问两个寄存器而不是执行&和>>操作?我问这个问题是因为对于我的项目,这部分代码将被执行一万亿次以上。所以这个疑问还是先验证一下比较好。
有什么汇编代码可以让我闲逛吗?
Tob*_*ght 10
尽管通过强制转换进行截断比长常量更容易阅读,但您所写的可能是最好的。根据经验,如果您编写的代码明显且清晰,那么编译器通常最容易看到您的意图并进行适当的优化。
在Compiler Explorer上,我提供了这个函数:
#include <stdint.h>
void decompose(__uint128_t num, uint64_t *a, uint64_t *b) {
*a = (uint64_t)(num >> 64);
*b = (uint64_t)num;
}
Run Code Online (Sandbox Code Playgroud)
当使用 编译为 x64 时gcc -O3,它会生成您想要的代码:
decompose:
mov QWORD PTR [rdx], rsi
mov QWORD PTR [rcx], rdi
ret
Run Code Online (Sandbox Code Playgroud)
Shift/mask 或联合是可行的方法。特别是如果您只想读取 的部分__int128,位操作是清晰的并且可以可靠地高效编译。
如果您要替换高位或低位 64 位,则 aunion可能比按位掩码/移位/或更容易让编译器看到。如果两种方式都能有效编译,我不会感到惊讶,但 aunion可能有利于人类可读性。
请注意,联合中两半的顺序将取决于字节顺序,而位移位则不然。
我建议使用uint64_torunsigned long long代替unsigned long,因为 Windows x64 使用 32 位long. 大多数其他 64 位 ABI 使用 LP64 ABI,但 32 位的另一种情况long是用于 64 位 CPU 的 ILP32 ABI,例如 AArch64 ILP32 和 x32 ABI。 sizeof(void*) = 4但__int128仍然支持。
我会使用强制转换来截断__int128为 64 位,而不必f在 中键入正确数量的 s 0xffffffffffffffff。对我来说,(uint64_t)a更好地遵循托比“显而易见、清晰”的指导方针。使强制转换变得明确,而不是仅仅分配给一个更窄的变量,对人类读者来说是有好处的。C 保证从较宽的整型类型到较窄的无符号类型的模归约,这意味着对无符号或 2 的补码有符号的源类型进行按位截断。(GCC 中的有符号整数始终是 2 的补码。)
a>>64完全没问题。即使对于有符号__int128,算术右移然后分配给 64 位类型也会丢弃高 64 个符号位,这些符号位可能是全 1 或全 0,GCC 仍然会对此进行优化。
#include <stdint.h>
uint64_t foo_signed (__int128 num) {
return (num >> 64) + (uint64_t)num;
// Intentionally sloppy in the abstract machine to see what happens:
// (u64)num is promoted back to 128-bit for + (with zero-extension because it's unsigned)
// then the + result truncated to uint64_t for return.
// GCC still avoids actually generating the high half of the signed shift result.
}
uint64_t foo_unsigned (unsigned __int128 num) {
return (num >> 64) + (uint64_t)num;
}
Run Code Online (Sandbox Code Playgroud)
这两个都编译为x86-64 的lea rax, [rdi + rsi]/ ret。(神箭)。
在现代 GNU C 中,手册目前只提到 ( unsigned) __int128,没有提到__uint128_t。
AFAIK,继续使用旧版并没有错__uint128_t;GCC 开发人员没有理由想要删除同一类型的该名称。请参阅gcc 中是否有 128 位整数?-__int128自 GCC4.6 以来就已经存在,目前它已经很老了。但除非您关心古老的 GCC 版本,否则我建议您unsigned __int128使用新代码,就像上面的示例一样。
在 ISO C23 中,unsigned _BitInt(128)将被标准化,因此您可能更喜欢这样。__int128但最后我检查了一下,只有 clang 支持它(但不限于 64 位目标__uint128_t)。
这使您可以_BitInt根据需要更改为便携式,并节省打字时间。
#ifdef defined(__SIZEOF_INT128__)
typedef unsigned __int128 u128;
// or __uint128_t for compat with even older GCC which doesn't define __SIZEOF_INT128__
#elif ??? // feature-test macro for this C23 feature?
typedef unsigned _BitInt(128) u128;
#else
#error no 128-bit integer type available
#endif
// then use u128 in later code.
Run Code Online (Sandbox Code Playgroud)
如果您发现移位和/或转换会给您的代码增加噪音,您可以编写辅助函数或宏。
static inline uint64_t hi64(u128 a) { return a >> 64; }
static inline uint64_t lo64(u128 a) { return (uint64_t)a; }
Run Code Online (Sandbox Code Playgroud)
然后你可以简单地使用hi64(x)和/或lo64(x)。