3

amd64 の GCC で使用可能な 128 ビット操作を取得しようとして、いくつかのインライン関数を実装しました。add_128_128_128 のように。最大限の柔軟性を得るために、どのレジスタを入力および出力として使用するかをコンパイラに決定させたいと考えました。そこで、複数の代替制約を使用しました。

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
        uint64_t a_hi = a >> 64;
        uint64_t a_lo = a;
        uint64_t b_hi = b >> 64;
        uint64_t b_lo = b;
        uint64_t retval_hi;
        uint64_t retval_lo;

        asm (
                "\n"
                "       add     %2, %0\n"
                "       adc     %3, %1\n"
                : "=r,r,r,r" (retval_lo)
                , "=r,r,r,r" (retval_hi)
                : "r,0,r,0" (a_lo)
                , "0,r,0,r" (b_lo)
                , "r,1,1,r" (a_hi)
                , "1,r,r,1" (b_hi)
        );

        return ((__uint128_t)retval_hi) << 64 | retval_lo;
}

これで、生成されたアセンブラー出力は次のようになります。

_Z11add_128_128oo:
        movq    %rdx, %rax
        movq    %rcx, %rdx
        add     %rdi, %rax
        adc     %rax, %rdx
        ret

私が困惑しているのは、ADC 命令を修正する方法です。これについて考えた結果、一致する制約でさえ「新しい」数値を取得するという一時的な結論に達しました。これにより、%rax が %3 == %0 == %rax であることが説明されます。それで、GCCに「r」制約のみをカウントするように指示する方法はありますか? (複数の代替制約をあきらめるだけで、このインライン アセンブリを機能させることができることはわかっています。)

ところで: GCC のインライン アセンブリに関する有用なドキュメントはありますか? 興味深いものに関しては、例がまったくない公式マニュアルは、このコンテキストでは役に立ちません。Google で検索しても何も見つかりませんでした。すべてのハウツーやものは、些細な基本的なことについて話しているだけで、複数の代替制約などのより高度なものは完全に省略されています。

4

3 に答える 3

2

最初に頭に浮かぶのは次のことです。

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
    asm("add %1, %%rax\n\t"
        "adc %2, %%rdx"
        : "+A"(a)
        : "r"((uint64_t)(b >> 64)), "r"((uint64_t)b)
        : "cc");
    return a;
}

これは、GCC が制約付きのRDX:RAX2 倍のサイズのレジスタ ペアとして扱うことができるためです。"A"これは、2 つのオペランドが交換可能であることを考慮しておらず、常に in を返すことにより、特にインライン展開では最適ではありませんが、レジスタの選択も制限されますRDXRAX

その可換性を取り入れるには、%制約修飾子を使用できます。

inline __uint128_t add_128_128_128(__uint128_t a, __uint128_t b) {
    uint64_t a_lo = a, a_hi = a >> 64, b_lo = b, b_hi = b >> 64;
    uint64_t r_lo, r_hi;
    asm("add %3, %0\n\t"
        "adc %5, %1"
        : "=r"(r_lo), "=r"(r_hi)
        : "%0" (a_lo), "r"(b_lo), "%1"(a_hi), "r"(b_hi) :
        : "cc");
    return ((__uint128_t)r_hi) << 64 | r_lo;
}

は、この%オペランドと次のオペランドが交換可能であることを GCC に示します。
これにより、次のコードが作成されます (インライン化されていません)。

セクション .text の分解:

0000000000000000 <add_128_128_128>:
   0: 48 89 f8 mov %rdi,%rax
   3: 48 01 d0 %rdx、%rax を追加
   6: 48 11 ce adc %rcx,%rsi
   9: 48 89 f2 mov %rsi,%rdx
   c: c3 retq

あなたが望んでいたものとかなり似ていますか?

于 2013-09-19T08:56:14.987 に答える
2

GMPや GCClonglong.hなどのプロジェクトに含まれるヘッダーを見てください。次のようなマクロがあります。

#define add_ssaaaa(sh, sl, ah, al, bh, bl) \
  __asm__ ("addq %5,%q1\n\tadcq %3,%q0"                                 \
           : "=r" (sh), "=&r" (sl)                                      \
           : "0"  ((UDItype)(ah)), "rme" ((UDItype)(bh)),               \
             "%1" ((UDItype)(al)), "rme" ((UDItype)(bl)))

これは、型を使用してインライン関数に変換するのに十分簡単なはず__uint128_tです。次のようなものを追加することをお勧めします:__attribute__ ((__always_inline__))コンパイラ フラグに関係なく、インライン化を強制します。


さらに、次の式に対して生成されたコードを見ましたa + bか? add/adcこの拡張タイプの動機の一部であった、必要な命令ペアが生成されることを期待しています。


関数呼び出しの結果は次のu128 x u64 -> u128とおりです (gcc-4.8.1) :

    imulq   %rdx, %rsi
    movq    %rdx, %rax
    mulq    %rdi
    addq    %rsi, %rdx
    ret

そしてu128 x u128 -> u128

imulq   %rdx, %rsi
movq    %rdi, %rax
imulq   %rdi, %rcx
mulq    %rdx
addq    %rcx, %rsi
addq    %rsi, %rdx
ret
于 2013-09-19T10:00:53.120 に答える
0

GCC には役に立たないかもしれませんが、CLANG を使用している人は、この発見に満足しているかもしれません: http://clang.llvm.org/docs/LanguageExtensions.html

これにより、ターゲットのアセンブラーを知らなくても、必要なものを実装できます。私はGCCのためにこのようなものを見つけることができませんでした:(

于 2013-12-08T22:51:35.437 に答える