インラインアセンブラ
先日のわんくま勉強会でアセンブラのセッションがありましたが、たいへん興味深く見させてもらいました。
アセンブラは触り程度にしか判らないのですが、久々にアセンブラの話しを聞いて思い出したのが、 Delphi-ML の以下のスレッド。わたしのレスもしっかり残ってますね(汗
10年ほど前のスレッドですが、このスレッドで始めてインラインアセンブラコードを見た時の驚きは忘れられません。
今も ML にいるか判りませんが、このコードを書いた pcode 氏には尊敬の念を抱いたものです。以下、pcode 氏が書いたコードの改造版です。
procedure CharReplace( var S: string; const Old, New: Char ); var ConvTbl : array[char] of char; var ch : char; P : PChar; begin for ch := low(char) to High(char) do ConvTbl[ch] := ch; ConvTbl[Old] := New; UniqueString(S); P := PChar(S); asm push eax push ebx push ecx push edx push esi push edi MOV ESI, P MOV ECX, [ESI - 4] LEA EDI, ConvTbl MOV EBX, ECX SHR ECX, 1 AND EBX, 1 JZ @@Loop MOVZX EAX, byte ptr [ESI] MOV BL, byte ptr [EDI + EAX] MOV byte ptr [ESI], BL INC ESI @@loop: MOVZX EAX, byte ptr [ESI] MOVZX EBX, byte ptr [ESI+1] MOV DL, byte ptr [EDI + EAX] MOV DH, byte ptr [EDI + EBX] MOV byte ptr[ESI], DL MOV byte ptr[ESI+1], DH DEC ECX LEA ESI, [ESI + 2] JNZ @@loop pop edi pop esi pop ecx pop edx pop ebx pop eax end; end;
pcode 氏は極めて辛口なレスが多いので嫌ってる人かなりいましたが、私は嫌いじゃなかったです。(でも苦手;)
思えば Delphi って、オブジェクト指向に対応し VB を遥かに凌駕するコンポーネントを用意している上に、ポインターも使えて Win32API も宣言なしで呼び出せ、さらにインラインアセンブラも書けてしまい、その上ネイティブコンパイラが異常に速く、一部の処理では C++ より速いという、当時としては鬼のような開発環境でした。
その頃、私はごんたさんという名古屋のエンジニアが作った DelphiARX というライブラリを使って AutoCAD のカスタマイズを行ってた訳ですが、ともすれば処理が重くなりがちな AutoCAD で如何にパフォーマンスを上げるか、日夜悩んでいただけに、インラインアセンブラを始めて見たときの感動はいまだに忘れることができません。
少し C++ で遊んでみました。
で、面白そうなので、セッション資料を基に C++ で少し遊んでみました。
#include <iostream> using namespace std; int main() { int a = 10; int b = 20; _asm { mov eax, a; add eax, b; mov b, eax; } printf( "%d\n", b ); return 0; }
結果
30
この辺は簡単ですね、EAX レジスタと ADD 命令を使った単純な加算処理です。お次は SUB 命令による減算処理です。
#include <iostream> using namespace std; int main() { int a = 10; int b = 3; _asm { mov eax, a; sub eax, b; mov a, eax; } printf( "%d\n", a ); return 0; }
結果
7
次は SHR 命令によるビットシフト演算です。SHR は右に指定ビット数分ずらします。下のサンプルは、右に1ビットずらしてます。
#include <iostream> using namespace std; int main() { int a = 10; _asm { mov eax, a; shr eax, 1; mov a, eax; } printf( "%d\n", a ); return 0; }
結果
5
続いて配列の演算。これもセッションでやってました。
#include <iostream> using namespace std; int main() { unsigned char src[] = { 100, 101, 102, 103, 104, 105, 106, 107 }; unsigned char dst[] = { 15, 16, 17, 18, 19, 20, 21, 22 }; __asm { movq mm0, dst; paddb mm0, src; movq dst, mm0; emms; } for (int i = 0; i < 8; i++ ){ printf( "%d\n", dst[i] ); } return 0; }
結果
115
117
119
121
123
125
127
129
これはほぼセッション資料どおりですが、この辺りになるとさすがに何やってるか判らなくなってくる(汗
C++/CLI で試してみました。
お次は C++/CLI でインラインアセンブラが使えるか試してみました。結果はマネージ関数内だとインラインアセンブラは使えない。別にネイティブ関数を用意してその中で使う形になるようです。
以下はダメ。ビルドできない。
#include "stdafx.h" using namespace System; // マネージ関数 int main(array<System::String ^> ^args) { int a = 10; int b = 20; _asm { mov eax, a; add eax, b; mov b, eax; } Console::WriteLine(b.ToString()); return 0; }
これなら OK
#include "stdafx.h" using namespace System; // ネイティブ関数 int sum(int a, int b) { _asm { mov eax, a; add eax, b; mov b, eax; } return b; } // マネージ関数 int main(array<System::String ^> ^args) { int a = sum(10, 20); Console::WriteLine(a.ToString()); return 0; }
結果
30
どうでしょう?さすがにアセンブラをゴリゴリ書いて実装したいとは思いませんが、アセンブラを通して CPU のことをより詳しく学んでみるのもたまにはいいかも知れません。アセンブラのより詳しい記事はこちらをどうぞ。