2.SSE2命令の実装
2-1.浮動小数点演算をアセンブラで書く(64ビット版)
1-4.今後のサンプルについて(64ビット版)で説明したように、この章のサンプルでは、プロジェクトの設定等は、変化がない限り説明しません。
ソリューションエクスプローラの
ソースファイルにある
Source.asmがアセンブラのコードです。
以下に内容を紹介します。
.code
;float funcSample
funcSample proc
subss xmm0, xmm1
ret
funcSample endp
end
C/C++側の
SampleMASM.cppは以下です。
#include <iostream>
extern "C" float funcSample(float, float);
int main()
{
std::cout << funcSample(70.5f, 50.0f) << "\n";
}
実行すると以下の出力があります。
20.5
...\ProjectMASM\Sample201\x64\Debug\SampleMASM.exe (プロセス 17512) は、コード 0 を伴って終了しました。
このウィンドウを閉じるには、任意のキーを押してください . . .
つまり
funcSample()関数は引き算をする関数です。
コード説明
この項から少しずつアセンブラの文法について紹介します。
まず、8086系のCPUには
セグメントという概念があります。
領域くらいの意味に考えてもらえればいいと思います。
CPUはマシン語しか読めません、マシン語はただの数字の羅列です。プログラムが実行されているときは、メモリ上にそのプログラムの数字群がロードされています。
しかしそれらの数字群には、
役割があります。
プログラムを実行する役割(コードセグメント)や
データとして使う役割(データセグメント)、
スタックとして使う役割(スタックセグメント)です。(ほかにありますが、まずこの3つを覚えましょう)。
それらの役割をする数字群を
領域で区切ります。それが
セグメントです。
セグメントはアセンブラ上では以下のように記述します。
名前 segument [オプション]
;内容を記述
名前 ends
オプションには
どういう役割をするセグメントかを記述します。
しかし、このドキュメントでは
Windowsアプリとして動くプログラムを記述します。
とすると、
C/C++言語(デスクトップアプリ)で作成したプログラムと互換性がなければなりません。
具体的には、アセンブラでは
.objという拡張子のオブジェクトファイル(中はマシン語でできている)を作成して、それをC/C++のコンパイラが作成した
.objファイルと
リンクすることで
実行ファイルを作成します。
ですので
セグメントの定義もそれに合わせる必要があります。
規約では
_TEXT segment
;...内容
_TEXT ends
のように記述します。これで
コードセグメントを作成することができます。
ですので、アセンブラコードでは
_TEXT segment
funcSample proc
subss xmm0, xmm1
ret
funcSample endp
_TEXT ends
end
と書いても問題なくビルドできます。もとのソースにあった
.codeは
コードセグメント定義を省略して書く手法です。
続く
funcSample procは
プロシージャの宣言です。
で
プロシージャを作成できます。
プロシージャは
C/C++の関数と同等と考えて差し支えありません。
関数は
引数と戻り値があります。この規約が問題でして、
x64環境のWindowsアプリは、かなり複雑になっています。
気になる人は
x64 での呼び出し規則(MSDN)がありますので調べてみましょう。
この規約によると
浮動小数点は、まず、XMM0からXMM3に渡されるとあります。XMM0等は
レジスタといわれる計算用の専用メモリです。というか、CPUというのは
レジスタ、メモリ、IOポートの3つしか扱えません。
メモリ上にあるデータは、いったん
レジスタにコピーしないと計算できないのです。ですが、レジスタはそんなにたくさんあるわけではありません。x64でも
全部で32個しかありません。これでも32ビット環境よりはずいぶん増えたのです。
このドキュメントでは
レジスタの説明は行いません。出てきたときに若干コメントします。
プロシージャ内を見てみましょう。
というのが記述されています。アセンブラの命令は、基本的に
ニーモニック(オペコード) オペランド1, オペランド2, ...
のように記述します。多くの場合、オペランドは3個以内です。
subssというニーモニックは
浮動小数点の引き算です。
subss op1,op2
;op1からop2を引いて結果をop1に入れる
という計算です。
C/C++の関数呼び出しで渡される引数は
なので
の結果は、
xmm0に収められます。
その後
が記述されています。これは
プロシージャから戻るという意味です。
戻り値は基本的に
xmm0に格納されます。
C/C++側では戻り値
xmm0を関数の戻り値と解釈し、
coutで出力します。