MASM, Visual C++におけるソフトウェア規約

Microsoft Macro Assembler（以降、MASMと略）Version 6以降でアプリケーションを開発する際のメモ書きです。
この記事では、Microsoftのソフトウェア規約について記します。

1. [16bit（Intel 8086、及びリアルモード）におけるソフトウェア規約](#16bitintel-8086及びリアルモードにおけるソフトウェア規約)
2. [32bit (プロテクトモード)におけるソフトウェア規約](#32bitプロテクトモードにおけるソフトウェア規約)
3. [64bit (x86-64)におけるソフトウェア規約](#64bitx86-64におけるソフトウェア規約)
4. [C/C++とMASMの型の関係](#ccとmasmの型の関係)
5. [C++における名前修飾](#cにおける名前修飾)
6. [Tips: Visual C++における最適化について](#tips-visual-cにおける最適化について)


# 16bit（Intel 8086、及びリアルモード）におけるソフトウェア規約

## 関数呼出規約

| MASM (langtype) | C(関数呼出規約)  | レジスタ | スタック   | スタックの破棄 | 名前修飾    |
|-----------------|-----------------|----------|------------|---------------|------------|
| C               | __cdecl         |          | 右からPUSH | 呼出側  | _`Label`    |
| PASCAL          | __pascal        |          | 左からPUSH | 関数側  | `Label`     |
| 非対応       | __fastcall  | `AX`, `DX`, `BX`  | 左からPUSH | 関数側  | `Label`     |

※16bit版のWindows APIの呼出規約は、`PASCAL`です。

## レジスタの用途

- 呼出元で保持　　：`AX`, `BX`, `CX`, `DX`, `ES`, `ST(0)`~`ST(7)`
- 関数内で保持　　：`SI`, `DI`, `BP`, `DS`
- 関数からの戻り値：`AX`, `DX`, `ST(0)` 

***戻り値について***

| 型                           | レジスタ |
|------------------------------|----------|
| char                         | AL |
| short int                    | AX |
| int                          | AX |
| long int                     | DX:AX  |
| float                        | DX:AX  |
| double                       | スタック経由 |
| long double                  | ST(0)  |
| near pointer                 | AX |
| far pointer                  | DX:AX  |
| 1Byteの構造体または共用体     | AL |
| 2Byteの構造体または共用体     | AX |
| 4Byteの構造体または共用体     | DX:AX  |
| その他の構造体または共用体    | スタック経由 |




# 32bit（プロテクトモード）におけるソフトウェア規約

## 関数呼出規約

| MASM (langtype) | C(関数呼出規約)  | レジスタ | スタック   | スタックの破棄 | 名前修飾    |
|-----------------|-----------------|----------|------------|---------------|------------|
| C               | __cdecl         |          | 右からPUSH | 呼出側  | _`Label`    |
| STDCALL         | __stdcall       |          | 右からPUSH | 関数側  | _`Label`@n  |
| 非対応 | __fastcall               | `ECX`, `EDX`                 | 右からPUSH | 関数側     | @`Label`@n  |
| 非対応 | __thiscall (引数が固定長) | `ECX` (thisとして)            | 右からPUSH | 関数側     | _`Label`@n  |
| 非対応 | __thiscall (引数が可変長) |        | 右からPUSH (thisは最後にPUSH )    | 関数側     | _`Label` |
| 非対応 | __vectorcall (VC++2013～) | `ECX`, `EDX`, `XMM0`～`XMM5` | 右からPUSH | 関数側     | `Label`@@n  |

- 名前修飾における関数名の最後の@nについて、nは引数の合計バイト数を10進数で記述する。尚、引数のサイズは実際のサイズであり、例えば32bitモードの時の`char`は、4Byte(32bit)の幅を持つ。

※32bit版のWindows APIの呼出規約は、`STDCALL`です。


***廃止された関数呼出規約(MASMでは使える)***

| MASM (langtype) | C(関数呼出規約) | レジスタ | スタック   | スタックの破棄 | 名前修飾    |
|-----------------|----------------|----------|------------|---------------|------------|
| SYSCALL         | __syscall      |          | 右からPUSH | 呼出側  | `Label`   |
| FORTRAN         | __fortran      |          | 左からPUSH | 関数側  | `Label`   |
| PASCAL          | __pascal       |          | 左からPUSH | 関数側  | `Label`   |

※MASMで非対応のlangtype(`__fastcall`, `__thiscall`等)は、別の関数呼出規約で書く必要がある。
　例えば`__fastcall`は、Visual C++では廃止された`SYSCALL`を使い、関数名の先頭に`@`、後ろに`@`と引数のサイズを10進数で書き、`ret`命令のパラメータには、スタックに詰まれた引数の数を指定して関数から戻るように記述する（[参考例：`PROC`~`ENDP`ディレクティブ](https://qiita.com/sha_w/items/14548760e5342badd458#%E4%BE%8B)）。

## レジスタの用途

- 呼出元で保持　　：`EAX`, `ECX`, `EDX`, `ST(0)`~`ST(7)`
- 関数内で保持　　：`EBX`, `ESI`, `EDI`, `EBP`
- 関数からの戻り値：`EAX`, `EDX`, `ST(0)`

***戻り値について***

| 型                             | レジスタ |
|--------------------------------|----------|
| bool                           | AL   |
| char                           | AL   |
| short int                      | AX   |
| int                            | EAX  |
| long int                       | EAX  |
| long long int                  | EDX:EAX  |
| float                          | ST(0) |
| double                         | ST(0) |
| long double                    | ST(0) |
| pointer                        | EAX  |
| 1Byteの構造体または共用体       | AL |
| 2Byteの構造体または共用体       | AX |
| 4Byteの構造体または共用体       | EAX  |
| 8Byteの構造体または共用体       | EDX:EAX  |
| その他の構造体または共用体      | スタック経由 |

## ストリーミングSIMD 拡張命令（Streaming SIMD Extensions)のソフトウェア規則

### 既存の呼出規約の拡張

- 最初の 3 つの`__m128`パラメータは、レジスタ`xmm0`, `xmm1`, および`xmm2`内で渡される。
- その他の`__m128`パラメータは、通常どおりスタック上で渡される。
- `__m128`の戻り値は、レジスタ`xmm0`内で渡される。
- レジスタ`xmm0`～`xmm7`は呼び出し元保存である。

### 呼出規約`__vectorcall`

*※Visual C++.net 2013より対応*

- 最初の 2 つの`DWORD`、またはこれより小さい引数は、`ECX`、及び`EDX`レジスタに渡される。
- さらに、最初の 6 つの浮動小数点型(`float`または`double`)及びベクター型の引数は、引数のサイズに応じたSIMDレジスタ0~5に渡される。
  - 浮動小数点型(`float`または`double`):`xmm`
  - `__m128`型：`xmm`
  - `__m256`型：`ymm`
- 残りの引数は、通常どおりスタック上で渡される。
- `__m128`, `float`, `double`の戻り値はレジスタ`xmm0`内、`__m256`の戻り値はレジスタ`ymm0`内で渡される。


# 64bit（x86-64）におけるソフトウェア規約

## 関数呼出規約

| MASM (langtype) | C(関数呼出し規約)  | レジスタ | スタック   | スタックの破棄 |
|-----------------|-------------------|----------|------------|---------------|
| 非対応 | Microsoft x64    | `RCX`, `RDX`, `R8`, `R9`, `XMM0`～`XMM4` | 右からPUSH  | 呼出側 |

## レジスタの用途

- 呼出元で保持　　：`RAX`, `RCX`, `RDX`, `R8`~`R11`, `XMM0`~`XMM5`, `YMM0`~`YMM5`
- 関数内で保持　　：`RBX`, `RSI`, `RDI`, `RBP`, `R12`~`R15`, `XMM6`~`XMM15`, `YMM6`~`YMM15`
- 関数からの戻り値：`RAX`, `XMM0`, `YMM0`

***戻り値について***

| 型                             | レジスタ |
|--------------------------------|----------|
| bool                           | AL   |
| char                           | AL   |
| short int                      | AX   |
| int                            | EAX  |
| long int                       | EAX  |
| long long int                  | RAX  |
| float                          | XMM0 |
| double                         | XMM0 |
| __mm64                         | RAX  |
| __mm128                        | XMM0 |
| __mm256                        | YMM0 |
| 1Byteの構造体または共用体       | AL   |
| 2Byteの構造体または共用体       | AX   |
| 4Byteの構造体または共用体       | EAX  |
| 8Byteの構造体または共用体       | RAX  |
| その他の構造体または共用体      | スタック経由 |

# C/C++とMASMの型の関係

| 型          | 16bit    | 32bit    | 64bit    |
|-------------|----------|----------|----------|
| bool        |          | (S)BYTE  | (S)BYTE  |
| char        | (S)BYTE  | (S)BYTE  | (S)BYTE  |
| wchar_t     |          | (S)WORD  | (S)WORD  |
| short int   | (S)WORD  | (S)WORD  | (S)WORD  |
|       int   | (S)WORD  | (S)DWORD | (S)DWORD |
| long  int   | (S)DWORD | (S)DWORD | (S)DWORD |
| long long int |        | (S)QWORD | (S)QWORD |
| enum        | (S)WORD  | (S)DWORD | (S)DWORD |
| float       | REAL4    | REAL4    | REAL4    |
| double      | REAL8    | REAL8    | REAL8    |
| long double | REAL10   | REAL8    | REAL8    |
| pointer     | PTR (2Byte) | PTR (4Byte) | PTR (8Byte) |
| near pointer | NEAR PTR (2Byte) |  |         |
| far  pointer | FAR  PTR (4Byte) |  |         |

# C++における名前修飾

C++においては、`extern "C"`を宣言しない限り、独自の名前修飾が行われる。
従って、C言語やアセンブリ言語で書かれた関数をC++から呼ぶ場合は、`extern`宣言に"C"を追加する必要がある。

```example1.cpp
extern "C" void func();
```

また、C++で書かれた関数を、C言語やアセンブリ言語から呼ぶ場合は、従来の名前修飾修飾にする為、関数に`extern "C"`と宣言する。

```example2.cpp
extern "C" __cdecl void func(){
    //statement;
}
```

# Tips: Visual C++における最適化について

以下の条件時について、以下の条件を満たす場合、

1. リンク時のコード生成を有効（`/LTCG`）をした場合、且つ、
2. ある関数において、その関数のある"*.obj"ファイル及び、その関数を呼ぶコードがある"*.obj"ファイルについて、コンパイル時に、リンク時のコード生成を有効（`/GT`）をした場合

Visual C++のコンパイラは、最適化の為、関数規約を無視し、関数の入出力について以下の最適化を行う。

- レジスターに引数を渡す（`__fastcall`とか関係無しに）。
- 配置の為に、引数の順番を並べ替える。
- 未使用の引数を削除する。

但し、一つでも、リンク時のコード生成が有効（`/GT`）でない"*.obj"ファイルがあった場合は、関数呼出規約が守られる。
例えば、アセンブリ言語で書かれたソースからアセンブルされた"*.obj"ファイル等。
つまり、アセンブリ言語で書くよりも、コンパイラとリンカに最適化を任せてしまった方が、 多くの場合は、アセンブリ言語で関数を記述するよりも実行速度が早くなる。


# 参考資料

- Microsoft®: MASM Version 6.1 Programmer’s Guide
- Intel Corporation: AP-589 ストリーミングSIMD 拡張命令のソフトウェア規則　バージョン2.1　1999年1月（注文番号: 243873J-002）
- [Microsoft Docs: 呼び出し規則](https://docs.microsoft.com/ja-jp/cpp/cpp/calling-conventions)