アセンブリ式
組み込みアセンブラは、すべての式を 32 ビットの整数値として評価します。 浮動小数点数値および文字列値(文字列定数は除く)はサポートしません。
式は、式の要素と演算子から構成されます。また、各式には、それに関連付けられた式クラスと式型があります。
Delphi の式とアセンブラの式の違い
Delphi の式と組み込みアセンブラの式との最も大きな違いは、アセンブラの式は 1 つの定数値に解決できなければならないという点です。 つまり、アセンブラの式は、コンパイル時に計算可能な値にならなければなりません。 たとえば、次のように宣言されているとします。
const
X = 10;
Y = 20;
var
Z: Integer;
次の文は有効です。
asm
MOV Z,X+Y
end;
X と Y の両方が定数のため、式 X + Y は、定数 30 を簡単に記述する手段の 1 つです。したがって、この命令が実行されると、単純に値 30 が変数 Z に転送されます。 一方、X と Y が次のように変数であるとします。
var
X, Y: Integer;
組み込みアセンブラは、コンパイル時に X + Y の値を計算できません。 この場合、X と Y の合計を Z に転送するには、次の命令を使用します。
asm
MOV EAX,X
ADD EAX,Y
MOV Z,EAX
end;
Delphi の式では、変数の参照はその変数の内容を表します。 一方、アセンブラの式では、変数の参照はその変数のアドレスを表します。 Delphi の式では、X + 4(X は変数)は、X の内容に 4 を加えることを意味します。一方、組み込みアセンブラでは、X のアドレスより 4 バイト上位のアドレスにあるワードの内容を意味します。 したがって、次のような式を記述したとします。
asm
MOV EAX,X+4
end;
このコードは、X に 4 を加えた値を AX に読み込むのではなく、X より 4 バイト上位のアドレスに格納されているワードの値を読み込みます。 X の内容に 4 を加える正しい方法は、次のようになります。
asm
MOV EAX,X
ADD EAX,4
end;
式の要素
式の要素には、定数、レジスタ、およびシンボルがあります。
数値定数
数値定数は整数でなければなりません。また、その値は、2,147,483,648 と 4,294,967,295 の間でなければなりません。
デフォルトでは、数値定数には 10 進数表記を使用しますが、組み込みアセンブラは、2 進数、8 進数、16 進数もサポートしています。 数字の後に B を付けると 2 進数表記、数字の後に O を付けると 8 進数表記が選択されます。数字の後に H を付けるか、数字の前に $ を付けると 16 進数表記が選択されます。
数値定数は、0 から 9 までの数字、または $ で始まらなければなりません。 H サフィックスを使用して 16 進数の定数を記述するときに、最初の有効数字が A から F までのいずれかになる場合は、その数字の前に 0 を付加する必要があります。 たとえば、 0BAD4H と $BAD4 は 16 進数の定数ですが、 BAD4H は文字で始まっているため識別子になります。
文字列定数
文字列定数は、一重引用符または二重引用符で囲む必要があります。 閉じる側の引用符として、同じ種類の引用符を 2 つ続けても 1 文字としてしかカウントされません。 文字列定数の例を以下に示します。
'Z'
'Delphi'
'Windows'
"That's all folks"
'"That''s all folks," he said.'''
'100'
'"'
"'"
DB 指令には任意の長さの文字列定数を記述できます。その結果、その文字列内の文字の ASCII 値を含むバイト列が割り当てられます。 それ以外の場合は、1 つの文字列定数が 4 文字を超えてはいけません。また、文字列定数は式に関与する数値を表します。 文字列定数の数値は次のように計算されます。
Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24
Ch1 は右端(最後)の文字、Ch4 は左端(最初)の文字を表しています。 文字列が 4 文字より短い場合は、左端の文字が 0 と見なされます。 文字列定数とその数値を次の表に示します。
文字列の例とその値
文字列 | 値 |
---|---|
'a' |
00000061H |
'ba' |
00006261H |
'cba' |
00636261H |
'dcba' |
64636261H |
'a ' |
00006120H |
' a' |
20202061H |
'a' * 2 |
000000E2H |
'a'-'A' |
00000020H |
not 'a' |
FFFFFF9EH |
レジスタ
次の予約語は、インライン アセンブラでは CPU レジスタを表します。
- CPU レジスタ
カテゴリ |
識別子 |
---|---|
8 ビット CPU レジスタ |
AH、AL、BH、BL、CH、CL、DH、DL(汎用レジスタ) |
16 ビット CPU レジスタ |
AX、BX、CX、DX(汎用レジスタ)、DI、SI、SP、BP(インデックス レジスタ)、CS、DS、SS、ES(セグメント レジスタ)、IP(命令ポインタ) |
32 ビット CPU レジスタ |
EAX、EBX、ECX、EDX(汎用レジスタ)、EDI、ESI、ESP、EBP(インデックス レジスタ)、FS、GS(セグメント レジスタ)、EIP |
FPU |
ST(0)、...、ST(7) |
MMX FPU レジスタ |
mm0、...、mm7 |
XMM レジスタ |
xmm0、...、xmm7(x64 の場合は、xmm15 までの 16 個) |
Intel 64 レジスタ |
RAX、RBX、... |
- x64 CPU 汎用レジスタ、x86 FPU データ レジスタ、および x64 SSE データ レジスタ
オペランドがレジスタ名のみで構成されている場合、それをレジスタ オペランドと呼びます。 すべてのレジスタは、レジスタ オペランドとして使用できます。それ以外のコンテキストで使用できるレジスタもあります。
ベース レジスタ(BX と BP)とインデックス レジスタ(SI と DI)は、角かっこで囲んで記述するとインデックスを表すことができます。 有効なベース/インデックス レジスタの組合せは、[BX]、[BP]、[SI]、[DI]、[BX+SI]、[BX+DI]、[BP+SI]、[BP+DI] です。 32 ビットのレジスタはすべてインデックスに使用できます。たとえば、[EAX+ECX]、[ESP]、[ESP+EAX+5]。
セグメント レジスタ(ES、CS、SS、DS、FS、GS)もサポートされていますが、32 ビット アプリケーションでは普通は役に立ちません。
シンボル ST は、8087 浮動小数点レジスタ スタックの最上位のレジスタを表します。 8 つの浮動小数点レジスタのそれぞれには、ST (X) を使用して参照できます。X は、レジスタ スタックの先頭からの距離を表す 0 から 7 までの定数。
シンボル
組み込みアセンブラでは、アセンブリ言語の式の中でほとんどすべての Delphi 識別子(定数、型、変数、手続き、関数を含む)にアクセスできます。 さらに、組み込みアセンブラには、特殊なシンボル @Result が実装されています。これは、関数の本体に記述される Result 変数に対応します。 たとえば、次の関数を考えます。
function Sum(X, Y: Integer): Integer;
begin
Result := X + Y;
end;
この関数はアセンブリ言語では次のように記述されます。
function Sum(X, Y: Integer): Integer; stdcall;
begin
asm
MOV EAX,X
ADD EAX,Y
MOV @Result,EAX
end;
end;
以下のシンボルは、asm 文の中では使用できません。
- 標準手続きおよび標準関数(Writeln、Chr など)。
- 文字列、浮動小数点数、集合定数(ただし、レジスタの読み込み時は除く)。
- 現在のブロック内で宣言されていないラベル。
- 関数の外での @Result シンボル。
asm 文の中で使用できるシンボルの種類を次の表に示します。
組み込みアセンブラで認識されるシンボル
シンボル | 値 | クラス | 型 |
---|---|---|---|
ラベル |
ラベルのアドレス |
メモリ参照 |
型のサイズ |
定数 |
定数の値 |
即値 |
0 |
型 |
0 |
メモリ参照 |
型のサイズ |
フィールド |
フィールドのオフセット |
メモリ |
型のサイズ |
変数 |
変数のアドレス、または変数へのポインタのアドレス |
メモリ参照 |
型のサイズ |
プロシージャ |
手続きのアドレス |
メモリ参照 |
型のサイズ |
関数 |
関数のアドレス |
メモリ参照 |
型のサイズ |
ユニット |
0 |
即値 |
0 |
@Result |
Result 変数のオフセット |
メモリ参照 |
型のサイズ |
最適化が無効になっている場合は、ローカル変数(手続きや関数の内部で宣言された変数)は常にスタック上に割り当てられ、EBP からの相対アドレスでアクセスされます。ローカル変数シンボルの値は EBP からの符号付きオフセットになります。 ローカル変数を参照するときは、アセンブラが自動的に [EBP] を加算します。 たとえば、次のような宣言があるとします。
var Count: Integer;
関数または手続きの内部に次の命令があるとします。
MOV EAX,Count
この命令は、MOV EAX,[EBP4] にアセンブルされます。
組み込みアセンブラは、var パラメータを 32 ビットのポインタとして扱います。var パラメータのサイズは常に 4 です。 var パラメータにアクセスする構文は、値パラメータにアクセスする構文とは異なります。 var パラメータの内容にアクセスするには、この 32 ビットのポインタを読み込んでから、それが指す場所にアクセスします。 例:
function Sum(var X, Y: Integer): Integer; stdcall;
begin
asm
MOV EAX,X
MOV EAX,[EAX]
MOV EDX,Y
ADD EAX,[EDX]
MOV @Result,EAX
end;
end;
これらの識別子は asm 文の内部で有効です。 たとえば、次のように宣言されているとします。
type
TPoint = record
X, Y: Integer;
end;
TRect = record
A, B: TPoint;
end;
var
P: TPoint;
R: TRect;
asm 文の中では、次の命令を使用してフィールドにアクセスできます。
MOV EAX,P.X
MOV EDX,P.Y
MOV ECX,R.A.X
MOV EBX,R.B.Y
型識別子は、変数を簡単に作成するために使用できます。 次の命令はいずれも、同じマシン コード([EDX] の内容を EAX に読み込む)を生成します。
MOV EAX,(TRect PTR [EDX]).B.X
MOV EAX,TRect([EDX]).B.X
MOV EAX,TRect[EDX].B.X
MOV EAX,[EDX].TRect.B.X
式クラス
組み込みアセンブラでは、式を 3 つのクラス(レジスタ、メモリ参照、即値)に分類しています。
1 つのレジスタ名のみで構成される式はレジスタ式です。 レジスタ式の例としては、AX、CL、DI、ES などがあります。 レジスタ式をオペランドとして使用すると、アセンブラは、CPU レジスタ上で演算を行う命令を生成します。
メモリの場所を表す式はメモリ参照です。 Delphi のラベル、変数、型付き定数、手続き、および関数はこのカテゴリに属します。
レジスタでもなく、メモリの場所にも関連付けられていない式は、即値です。 このグループには、Delphi の型なし定数と型識別子が含まれます。
即値とメモリ参照では、オペランドとして使用した場合に、異なるコードが生成されます。 例:
const
Start = 10;
var
Count: Integer;
// …
asm
MOV EAX,Start { MOV EAX,xxxx }
MOV EBX,Count { MOV EBX,[xxxx] }
MOV ECX,[Start] { MOV ECX,[xxxx] }
MOV EDX,OFFSET Count { MOV EDX,xxxx }
end;
Start は即値のため、最初の MOV は、即値の転送命令に変換されます。 一方、2 番目の MOV は、Count がメモリ参照のため、メモリの転送命令に変換されます。 3 番目の MOV では、角かっこによって Start がメモリ参照に変換されます(この場合は、データ セグメントのオフセット10 にあるワード)。 4 番目の MOV では、OFFSET 演算子によって Count が即値(データ セグメントのオフセット Count)に変換されます。
角かっこと OFFSET 演算子は、互いに打ち消し合います。 次の asm 文は、前の asm 文の最初の 2 行と同じマシン コードを生成します。
asm
MOV EAX,OFFSET [Start]
MOV EBX,[OFFSET Count]
end;
メモリ参照と即値は、再配置可能なものと絶対位置にあるものにさらに分類されます。 再配置とは、リンカがシンボルに絶対アドレスを割り当てる処理のことです。 再配置可能な式は、リンク時に再配置が必要な値を表します。一方、絶対位置にある式は、そのような再配置が必要ない値を表します。 一般に、ラベル、変数、手続き、または関数を参照する式は再配置可能です。これらのシンボルの最終的なアドレスは、コンパイル時には不明だからです。 定数だけの演算を行う式は絶対位置にある式です。
組み込みアセンブラでは、絶対位置にある値に対する演算を実行できます。ただし、再配置可能な値に対する演算は、定数の加算と減算に限られています。
式型
どの組み込みアセンブラ式にも型(正確にはサイズ)があります。アセンブラは、式の型を単純にメモリ位置のサイズと見なします。 たとえば、整数変数の型は、4 バイトを占有するため、4 になります。 組み込みアセンブラは、可能な場合は常に型チェックを行います。次のような命令を考えます。
var
QuitFlag: Boolean;
OutBufPtr: Word;
// …
asm
MOV AL,QuitFlag
MOV BX,OutBufPtr
end;
アセンブラは、QuitFlag のサイズは 1(1 バイト)、OutBufPtr のサイズは 2(1 ワード)であることをチェックします。 次の命令を考えます。
MOV DL,OutBufPtr
DL は、1 バイト サイズのレジスタで、OutBufPtr は 1 ワードのため、この式はエラーになります。 メモリ参照の型は、型キャストによって変更できます。前の命令の適切な記述方法は次のようになります。
MOV DL,BYTE PTR OutBufPtr
MOV DL,Byte(OutBufPtr)
MOV DL,OutBufPtr.Byte
これらの MOV 命令はすべて OutBufPtr 変数の最初(最下位)のバイトを参照します。
場合によっては、メモリ参照に型がないこともあります。 角かっこで囲まれた即値(Buffer)の例を次に示します。
procedure Example(var Buffer);
asm
MOV AL, [Buffer]
MOV CX, [Buffer]
MOV EDX, [Buffer]
end;
組み込みアセンブラではこれらの命令が許されます。式 [Buffer] には型がないからです。 [Buffer] は "Buffer で表される場所の内容" を意味します。その型は、最初のオペランドから判別できます(AL の場合はバイト、CX の場合はワード、EDX の場合はダブルワード)。
もう一方のオペランドから型を判別できない場合は、組み込みアセンブラは明示的な型キャストを要求します。 例:
INC BYTE PTR [ECX]
IMUL WORD PTR [EDX]
現在宣言されている Delphi の型のほかに、組み込みアセンブラが提供する定義済みの型シンボルを次の表に示します。
定義済みの型シンボル
シンボル | 型 |
---|---|
BYTE |
1 |
Word |
2 |
DWORD |
4 |
QWORD |
8 |
TBYTE |
10 |
式の演算子
組み込みアセンブラは、さまざまな演算子を提供しています。 優先規則は、Delphi 言語のものと異なります。たとえば、asm 文では、AND は、加算演算子や減算演算子よりも優先度が低くなります。 次の表は、組み込みアセンブラの式の演算子を優先度が高い順に示したものです。
組み込みアセンブラの式の演算子の優先順位
演算子 | 説明 | 優先順位 |
---|---|---|
& |
最高 | |
(... ), [... ],., HIGH, LOW |
||
+, - |
単項の + と - |
|
: |
||
OFFSET、TYPE、PTR、*、/、MOD、SHL、SHR、+、- |
二項の + と - |
|
NOT、AND、OR、XOR |
最低 |
組み込みアセンブラの式の演算子の定義を次の表に示します。
組み込みアセンブラの式の演算子の定義:
演算子 | 説明 |
---|---|
& |
識別子のオーバーライド。 アンパサンドの直後の識別子は、たとえ、組み込みアセンブラの予約語と同じスペルであっても、ユーザー定義のシンボルとして扱われます。 |
(... ) |
サブ式。 かっこ内の式は、評価されてから 1 つの式要素として扱われます。 かっこ内の式の前に別の式を置くこともできます。その場合、結果はこの 2 つの式の値の合計になり、最初の式の型を持ちます。 |
[... ] |
メモリ参照。 角かっこ内の式は、評価されてから 1 つの式要素として扱われます。 角かっこ内の式の前に別の式を置くこともできます。その場合、結果はこの 2 つの式の値の合計になり、最初の式の型を持ちます。 結果は常にメモリ参照です。 |
. |
構造体メンバのセレクタ。 結果は、ピリオドの前の式とピリオドの後の式の合計になり、ピリオドの後の式の型を持ちます。 ピリオドの前の式で識別されるスコープに属するシンボルには、ピリオドの後の式でアクセスできます。 |
HIGH |
演算子の後のワードサイズの式の上位 8 ビットを返します。 この式は、絶対位置にある即値でなければなりません。 |
LOW |
演算子の後のワードサイズの式の下位 8 ビットを返します。 この式は、絶対位置にある即値でなければなりません。 |
+ |
単項のプラス。 プラスの後の式を変更せずに返します。 この式は、絶対位置にある即値でなければなりません。 |
- |
単項のマイナス。 マイナスの後の式の値の符号を反転させたものを返します。 この式は、絶対位置にある即値でなければなりません。 |
+ |
加算。 この式は、即値またはメモリ参照になります。ただし、式の中の 1 つだけは、再配置可能な値でもかまいません。 式の中の 1 つが再配置可能な値の場合は、結果も再配置可能な値になります。 式の中のいずれかがメモリ参照の場合は、結果もメモリ参照になります。 |
- |
減算。 最初の式は任意のクラスを持つことができますが、2 番目の式は絶対位置にある即値でなければなりません。 結果は、最初の式と同じクラスを持ちます。 |
: |
セグメントのオーバーライド。 コロンの後の式が、コロンの前のセグメント レジスタ名(CS、DS、SS、FS、GS、または ES)で与えられたセグメントに属するように、アセンブラに指示します。 結果は、コロンの後の式の値を持つメモリ参照になります。 セグメント オーバーライドが命令のオペランドで使われた場合は、指定されたセグメントが確実に選択されるように、その命令の前に、適切なセグメント オーバーライド プレフィックス命令を付けます。 |
オフセット |
演算子の後の式のオフセット部分(ダブルワード)を返します。 結果は即値です。 |
TYPE |
演算子の後の式の型(バイト数)を返します。 即値の型は 0 です。 |
PTR |
型キャスト演算子。 結果は、演算子の後の式の値を持つメモリ参照になり、演算子の前の式の型を持ちます。 |
* |
乗算。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
/ |
整数の除算。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
MOD |
整数で除算した後の剰余。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
SHL |
論理左シフト。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
SHR |
論理右シフト。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
NOT |
ビット反転。 この式は絶対位置の即値でなければなりません。結果は絶対位置の即値になります。 |
AND |
ビット AND。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
OR |
ビット OR。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |
XOR |
ビット単位の排他的論理和。 両方の式が絶対位置の即値でなければなりません。結果は、絶対位置の即値になります。 |