7

我在 GCC 中使用各种双机器字类型,例如 x86_64 上的 (u)int128_t 和 i386、ARM 上的 (u)int64_t 等。我正在寻找一种正确/便携/干净的方式来访问和操作各个实际机器字(主要是在汇编程序中)。例如,在 32 位机器上,我想直接访问 gcc 内部使用的 int64_t 的高/低 32 位部分,而不使用像下面这样容易出错的愚蠢代码。同样,对于“本机”128 位类型,我想访问 gcc 正在使用的 64b 部分(对于下面的示例,“添加”很简单,但通常如此)。

考虑以下代码中的 32bit ASM 路径,将两个 int128_t 加在一起(可能是 gcc 的“native”,机器的“native”或机器的“half native”);这是可怕且难以维护(而且速度较慢)。

#define BITS 64
#if defined(USENATIVE)
// USE "NATIVE" 128bit GCC TYPE
typedef __int128_t int128_t;
typedef __uint128_t uint128_t;
typedef int128_t I128;
     #define HIGH(x) x
     #define HIGHVALUE(x) ((uint64_t)(x >> BITS))
     #define LOW(x) x
     #define LOWVALUE(x) (x & UMYINTMAX)
#else
typedef struct I128 {
    int64_t high;
    uint64_t low;
} I128;
     #define HIGH(x) x.high
     #define HIGHVALUE(x) x.high
     #define LOW(x) x.low
     #define LOWVALUE(x) x.low
#endif
     #define HIGHHIGH(x) (HIGHVALUE(x) >> (BITS / 2))
     #define HIGHLOW(x) (HIGHVALUE(x) & 0xFFFFFFFF)
     #define LOWHIGH(x) (LOWVALUE(x) >> (BITS / 2))
     #define LOWLOW(x) (LOWVALUE(x) & 0xFFFFFFFF)

inline I128 I128add(I128 a, const I128 b) {
#if defined(USENATIVE)
    return a + b;
#elif defined(USEASM) && defined(X86_64)
    __asm(
            "ADD %[blo], %[alo]\n"
            "ADC %[bhi], %[ahi]"
            : [alo] "+g" (a.low), [ahi] "+g" (a.high)
            : [blo] "g" (b.low), [bhi] "g" (b.high)
            : "cc"
            );
    return a;
#elif defined(USEASM) && defined(X86_32)
    // SLOWER DUE TO ALL THE CRAP
    int32_t ahihi = HIGHHIGH(a), bhihi = HIGHHIGH(b);
    uint32_t ahilo = HIGHLOW(a), bhilo = HIGHLOW(b);
    uint32_t alohi = LOWHIGH(a), blohi = LOWHIGH(b);
    uint32_t alolo = LOWLOW(a), blolo = LOWLOW(b);
    __asm(
            "ADD %[blolo], %[alolo]\n"
            "ADC %[blohi], %[alohi]\n"
            "ADC %[bhilo], %[ahilo]\n"
            "ADC %[bhihi], %[ahihi]\n"
            : [alolo] "+r" (alolo), [alohi] "+r" (alohi), [ahilo] "+r" (ahilo), [ahihi] "+r" (ahihi)
            : [blolo] "g" (blolo), [blohi] "g" (blohi), [bhilo] "g" (bhilo), [bhihi] "g" (bhihi)
            : "cc"
            );
    a.high = ((int64_t)ahihi << (BITS / 2)) + ahilo;
    a.low = ((uint64_t)alohi << (BITS / 2)) + alolo;
    return a;
#else
    // this seems faster than adding to a directly
    I128 r = {a.high + b.high, a.low + b.low};
    // check for overflow of low 64 bits, add carry to high
    // avoid conditionals
    r.high += r.low < a.low || r.low < b.low;
    return r;
#endif
}

请注意,我很少使用 C/ASM,事实上这是我第一次尝试内联 ASM。习惯于 Java/C#/JS/PHP 等意味着对于例行 C 开发人员来说非常明显的东西对我来说可能并不明显(除了代码风格中明显的不安全怪癖;))。此外,所有这些都可以完全称为其他东西,因为我很难在网上找到有关该主题的任何内容(非母语人士也是如此)。

非常感谢!

编辑 1

经过大量挖掘后,我发现了以下理论解决方案,该解决方案有效,但不必要的慢(比更长的 gcc 输出慢!)因为它强制所有内容到内存,我正在寻找一个通用解决方案(reg/mem/可能是 imm) . 我还发现,如果您在 32 位机器上对例如 64 位 int 使用“r”约束,gcc 实际上会将这两个值放在 2 个寄存器中(例如 eax 和 ebx)。问题在于无法可靠地访问第二部分。我确信有一些隐藏的运算符修饰符很难找到告诉 gcc 我想访问第二部分。

    uint32_t t1, t2;
    __asm(
            "MOV %[blo], %[t1]\n"
            "MOV 4+%[blo], %[t2]\n"
            "ADD %[t1], %[alo]\n"
            "ADC %[t2], 4+%[alo]\n"
            "MOV %[bhi], %[t1]\n"
            "MOV 4+%[bhi], %[t2]\n"
            "ADC %[t1], %[ahi]\n"
            "ADC %[t2], 4+%[ahi]\n"
            : [alo] "+o" (a.low), [ahi] "+o" (a.high), [t1] "=&r" (t1), [t2] "=&r" (t2)
            : [blo] "o" (b.low), [bhi] "o" (b.high)
            : "cc"
            );
    return a;
4

1 回答 1

1

我听说这个网站并不是真正的“代码审查”,但由于这对我来说是一个有趣的方面,我想我可以提供一些建议:

在 32 位版本中,您可以执行HIGHHIGHand co。巧妙地叠加 int/uint 数组,而不是移位和与运算。使用联合是一种方法,另一种是指针“魔术”。由于代码的汇编程序部分首先不是特别可移植,因此以类型双关语转换或不可移植联合的形式使用不可移植代码并不是什么大不了的事。

编辑:依靠单词中的位置也可以。所以例如,只在寄存器中传入输入和输出的地址,然后使用 (%0+4) 和 (%1+4) 完成其余部分绝对是一种选择。

如果你必须为乘法和除法做这个当然会变得更有趣......我不确定我想去那里......

于 2012-12-23T18:43:51.570 回答