8

MULQ命令を効率的に使用するために、GCC用のインラインx86-64アセンブリを作成しようとしています。MULQは、64ビットレジスタRAXに別の64ビット値を乗算します。他の値は、任意の64ビットレジスタ(RAXも含む)またはメモリ内の値にすることができます。MULQは、製品の上位64ビットをRDXに入れ、下位64ビットをRAXに入れます。

これで、正しいmulqをインラインアセンブリとして表現するのは簡単です。

#include <stdint.h>
static inline void mulq(uint64_t *high, uint64_t *low, uint64_t x, uint64_t y)
{
    asm ("mulq %[y]" 
          : "=d" (*high), "=a" (*low)
          : "a" (x), [y] "rm" (y)    
        );
}

このコードは正しいですが、最適ではありません。MULQは可換であるため、yたまたまRAXに含まれている場合は、そのままyにして乗算を実行するのが正しいでしょう。しかし、GCCはそれを認識していないため、オペランドを事前定義された場所に移動するための追加の命令を発行します。GCCに、一方がRAXになり、MULQがもう一方の場所を参照している限り、どちらの入力もどちらの場所にも配置できることを伝えたいと思います。GCCには、「複数の代替制約」と呼ばれるこの構文があります。カンマに注意してください(ただし、asm()全体が壊れています。以下を参照してください)。

asm ("mulq %[y]" 
      : "=d,d" (*high), "=a,a" (*low)
      : "a,rm" (x), [y] "rm,a" (y)    
    );

残念ながら、これは間違っています。GCCが2番目の代替制約を選択した場合、「mulq%rax」を発行します。明確にするために、この関数を検討してください。

uint64_t f()
{
    uint64_t high, low;
    uint64_t rax;
    asm("or %0,%0": "=a" (rax));
    mulq(&high, &low, 7, rax);
    return high;
}

でコンパイルされるとgcc -O3 -c -fkeep-inline-functions mulq.c、GCCは次のアセンブリを出力します。

0000000000000010 <f>:
  10:   or     %rax,%rax
  13:   mov    $0x7,%edx
  18:   mul    %rax
  1b:   mov    %rdx,%rax
  1e:   retq

「mul%rax」は「mul%rdx」である必要があります。

このインラインasmをどのように書き直して、すべての場合に正しい出力を生成できるでしょうか。

4

5 に答える 5

4
__asm__ ("mulq %3" : "=a,a" (*low), "=d,d" (*high) : "%0,0" (x), "r,m" (y))

longlong.hこれは、さまざまな GNU パッケージに含まれているものと似ています。本当にclangの利益のためではありません"r,m"here で"rm"説明されているように、複数の制約構文は依然としてclangにとって重要であるようです。これは残念ですが、gcc よりも clang の方が (特に x86[-86] で) 制約の一致が悪いことがわかります。gcc の場合:

__asm__ ("mulq %3" : "=a" (*low), "=d" (*high) : "%0" (x), "rm" (y))

(y)レジスターの圧力が高すぎない限り、十分であり、レジスターに保持することを好むでしょう。しかし、多くの場合、clangは常に流出しているようです。私のテストで"r"は、複数の制約構文の最初のオプションが選択されることが示されています。

"%3"命令の被乗数として、ゼロを基準とした3 番目のオペランドによってエイリアス化されたレジスタ (優先) またはメモリ位置のいずれかが許可されます。これはです。「ゼロ番目」のオペランド:をエイリアスします。これは明示的に、つまり64 ビット用です。の先頭の文字は交換演算子です。つまり、(x) は、レジスタ割り当てに役立つ場合は (y) と交換できます。明らかに、は次のように可換です。(y)"0"(*low)"a"%rax%"%0"mulqx * y == y * x

ここでは実際にはかなり制約があります。の値mulqで 64 ビットのオペランドを乗算して、128 ビットの積を生成します。これは、 を にロードする必要があり、64 ビット レジスタまたはメモリ アドレスにロードする必要があることを意味します。ただし、 、および次の入力が通勤する可能性があることを意味します。%3%rax%rdx:%rax"0" (x)(x)%rax(y)%0(x)(y)

また、私が見つけた最も実用的なインライン アセンブリ チュートリアルも参照します。gcc参照は「信頼できる」ものですが、チュートリアルとしては不十分です。


元の制約順序のエラーを拾ってくれたChrisに感謝します。

于 2013-04-07T17:05:06.237 に答える
3

インライン asm 構文に関する一般的な質問とは別に:

実際には、64x64 => 128 ビットの乗算にはインライン asm は必要ありません。GCC/clang/ICCは、単一の命令
に最適化する方法を知っています。2 つの GNU C 拡張機能 (インライン asm と. https://gcc.gnu.org/wiki/DontUseInlineAsma * (unsigned __int128)bmul__int128

unsigned __int128 foo(unsigned long a, unsigned long b) {
    return a * (unsigned __int128)b;
}

Godbolt コンパイラー エクスプローラーで、 gcc/clang/ICC をこれにコンパイルします。

# gcc9.1 -O3  x86-64 SysV calling convention
foo(unsigned long, unsigned long):
        movq    %rdi, %rax
        mulq    %rsi
        ret                         # with the return value in RDX:RAX

または上位半分を返す

unsigned long umulhi64(unsigned long a, unsigned long b) {
    unsigned __int128 res = a * (unsigned __int128)b;
    return res >> 64;
}

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

GCC はここで何が起こっているかを完全に理解しており、これ*は可換であるため、レジスタに 1 つしかなく、もう 1 つがない場合は、いずれかの入力をメモリ オペランドとして使用できます。

残念ながら、レジスタまたはメモリからの入力に応じて異なる asm テンプレートを使用することは一般的に不可能です。したがって、別の戦略を完全に使用することはできません (たとえば、何か整数を実行する代わりに、SIMD レジスタに直接ロードするなど)。

マルチオルタナティブ制約はかなり制限されており、主に のような命令のメモリソースとメモリ宛先のバージョンadd、またはそのようなものにのみ適しています。

于 2019-07-04T00:52:54.300 に答える