web-dev-qa-db-ja.com

アセンブリ言語-モジュロの実行方法

X86アセンブリにはモジュロ演算子または命令のようなものがありますか?

34
enne87

モジュラス/除数が既知の定数であり、パフォーマンスに関心がある場合は、 this および this を参照してください。乗数の逆数は、実行時までわからないループ不変値に対しても可能です。 https://libdivide.com/ を参照してください(ただし、JIT code-genがないと、1つの定数に必要なステップだけをハードコーディングするよりも効率が低下します)。

既知の2のべき乗に対してdivを使用しないでください。muchは、andよりも遅い、または右除算のためのシフト。 2のべき乗による符号なしまたは符号付き除算の例については、Cコンパイラの出力をご覧ください。 Godboltコンパイラエクスプローラー 。ランタイム入力が2のべき乗であることがわかっている場合は、_lea eax, [esi-1]_を使用します。 _and eax, edi_またはx & (y-1)を実行するようなもの。モジュロ256はさらに効率的です。2つのレジスタが分離されている限り、_movzx eax, cl_は最近のIntel CPUでゼロレイテンシを持ちます(mov-elimination)。


単純/一般的な場合:実行時の不明な値

DIV命令(およびそれに対応する IDIV 符号付き数値)は商と剰余の両方を与えます。符号なしの場合、剰余とモジュラスは同じものです。符号付きidivの場合、 剰余(モジュラスではない) になります。
例えば。 _-5 / 2 = -2 rem -1_。 x86除算のセマンティクスは、C99の_%_演算子と完全に一致します。

_DIV r32_は、_EDX:EAX_の64ビット数を(レジスタまたはメモリ内の)32ビットオペランドで除算し、商をEAXに、残りをEDXに格納します。商がオーバーフローするとエラーになります。

符号なし32ビットの例(任意のモードで動作)

_mov eax, 1234          ; dividend low half
mov edx, 0             ; dividend high half = 0.  prefer  xor edx,edx

mov ebx, 10            ; divisor can be any register or memory

div ebx       ; Divides 1234 by 10.
        ; EDX =   4 = 1234 % 10  quotient
        ; EAX = 123 = 1234 / 10  remainder
_

16ビットアセンブリでは、_div bx_を実行して、EBXで_EDX:EAX_の32ビットオペランドを除算できます。詳細については、Intel Architectures Software Developer's Manuals を参照してください。

通常、常に符号なしdivの前に_xor edx,edx_を使用して、EAXをEDX:EAXにゼロ拡張します。これは、「通常の」32ビット/ 32ビット=> 32ビット除算を行う方法です。

署名された部門の場合、cdqidivの前に使用して、からsign-EAXをEDX:EAXに拡張します。 DIV命令を使用する前にEDXを0にする必要がある理由 も参照してください。他のオペランドサイズについては、cbw(AL-> AX)、cwd(AX-> DX:AX)、cdq(EAX-> EDX:EAX)、またはcqo(RAX-> RDX:RAX)は、上半分を下半分の符号ビットに応じて_0_または_-1_に設定します。

div/idivは、8、16、32、および(64ビットモードで)64ビットのオペランドサイズで使用できます。 64ビットのオペランドサイズは、現在のIntel CPUでは32ビット以下よりもはるかに遅いですが、AMD CPUはオペランドサイズに関係なく、実際の数値の大きさのみを考慮します。

8ビットのoperand-sizeは特別であることに注意してください。暗黙の入出力は、DL:ALではなく、AH:AL(別名AX)にあります。例については、「 8086 Assembly on DOSBox:Bug with idiv instruction? 」を参照してください。

署名付き64ビット除算の例(64ビットモードが必要)

_   mov    rax,  0x8000000000000000   ; INT64_MIN = -9223372036854775808
   mov    ecx,  10           ; implicit zero-extension is fine for positive numbers

   cqo                       ; sign-extend into RDX, in this case = -1 = 0xFF...FF
   idiv   rcx
       ; quotient  = RAX = -922337203685477580 = 0xf333333333333334
       ; remainder = RDX = -8                  = 0xfffffffffffffff8
_

制限/よくある間違い

_div dword 10_はエンコードできないマシンコードになります(したがって、アセンブラは無効なオペランドに関するエラーを報告します)。

mul/imulとは異なり(通常、高速な2オペランド_imul r32, r/m32_または3オペランド_imul r32, r/m32, imm8/32_を使用する必要があります。 )、上半分の被除数入力のない即値、または32ビット/ 32ビット=> 32ビット除算または剰余による除算のための新しいオペコードはありません。

除算は非常に遅く、(できれば)まれであるため、EAXとEDXを回避する方法を追加したり、直接イミディエイトを使用したりすることはありません。


divとidivは、商が1つのレジスタに収まらない場合にエラーになります(AL/AX/EAX/RAX、被除数と同じ幅)。これにはゼロによる除算が含まれますが、ゼロ以外のEDXおよびより小さい除数でも発生します。これが、Cコンパイラが32ビット値をDX:AXに分割する代わりに、ゼロ拡張または符号拡張するだけの理由です。

また、_INT_MIN / -1_がCの未定義の動作である理由:x86のような2の補数システムの符号付き商がオーバーフローします。 x-86とARMの例については、 -1(負の整数)による整数除算の結果がFPEになる理由 を参照してください。この場合、x86 idivは実際にフォールトします。

X86例外は_#DE_-除算例外です。 Unix/Linuxシステムでは、カーネルは#DE例外を引き起こすプロセスにSIGFPE算術例外信号を送信します。 ( どのプラットフォームで整数によるゼロ除算が浮動小数点例外をトリガーしますか?

divの場合、_high_half < divisor_で配当を使用しても安全です。例えば_0x11:23 / 0x12_は_0xff_より小さいため、8ビットの商に収まります。

1つのチャンクの剰余を次のチャンクの上位半分の被除数(EDX)として使用することで、巨大な数値を小さな数値で拡張精度で除算することができます。これがおそらく、彼らが他の方法ではなく残り= EDX quotient = EAXを選んだ理由です。

66
user786653

2のべき乗を法として計算する場合、ビット単位のANDを使用する方が、除算を実行するよりも簡単で一般的に高速です。 bが2の累乗の場合、a % b == a & (b - 1)

たとえば、レジ​​スタEAX、モジュロ64の値を取得してみましょう。
最も簡単な方法はAND EAX, 63、63はバイナリで111111であるため。

マスクされた上位の数字は、私たちにとって関心のないものです。やってみて!

同様に、2のべき乗でMULまたはDIVを使用する代わりに、ビットシフトを使用する方法があります。ただし、符号付き整数に注意してください!

26
Andreiasw