TOP MAP UP

AMD64 Software Visual Studio .NET 2005 Professional beta1 でアセンブラ 編

 いきなり衝撃の事実だが、なんとVisual Studio .NET 2005(以下VS8) よりインラインアセンブラが使えない。まぁ個人的にはインラインアセンブラは、最初にCで目的のコードを書いて、それのアセンブラコードを見て、入出力の部分を押さえてから(スタックの積み方とか暗記してれば必要ないが:汗)インラインアセンブラ化して、そこから更に可能であればnakid化等の最適化を施す・・・・というちょっとハック臭い決して王道とは思えないアプローチになるので、なるべく正攻法を学習させるという意味では無くなってもまぁ良いのかな?と思うが、単純に選択肢が減るという部分については残念に思う。



 VS8においてアセンブラを使用するアプローチとしては、まず、C++でプロトタイプ宣言等を行う(例としてA-SATURN G3等で使っているメモリエミュレーション部分)
#ifndef		_AZU_MEMORY_
#define		_AZU_MEMORY_

#include	"../device.h"
#define	DRC_UNIT	0x00000010

class	MEMORY	:	public	DEVICE	{
	public:
		unsigned char *		mem;
		unsigned long		mem_size;
#ifdef	DYNAMIC_RECOMPILE
		unsigned char *		drc;
		unsigned long		drc_size;
#endif
		//今回は↓が対象
		unsigned char	_r1(unsigned char *,unsigned long);
		unsigned short	_r2(unsigned char *,unsigned long);
		unsigned long	_r4(unsigned char *,unsigned long);
		void	_w1(unsigned char *,unsigned long,unsigned char);
		void	_w2(unsigned char *,unsigned long,unsigned short);
		void	_w4(unsigned char *,unsigned long,unsigned long);
		//今回は↑が対象

		MEMORY(unsigned long,unsigned long);
		~MEMORY();
};

#endif		_AZU_MEMORY_


 この宣言部分を含むソースのプロパティを図のように設定してアセンブラとC++コードを含むリスティングファイルを出力する


 で、このリスティングファイルを参照すると・・・・・
;Win32
EXTRN	?_w4@MEMORY@@UAEXPAEKK@Z:PROC	; MEMORY::_w4
EXTRN	?_w2@MEMORY@@UAEXPAEKG@Z:PROC	; MEMORY::_w2
EXTRN	?_w1@MEMORY@@UAEXPAEKE@Z:PROC	; MEMORY::_w1
EXTRN	?_r4@MEMORY@@UAEKPAEK@Z:PROC	; MEMORY::_r4
EXTRN	?_r2@MEMORY@@UAEGPAEK@Z:PROC	; MEMORY::_r2
EXTRN	?_r1@MEMORY@@UAEEPAEK@Z:PROC	; MEMORY::_r1



;AMD64
EXTRN	?_w4@MEMORY@@UEAAXPEAEKK@Z:PROC	; MEMORY::_w4
EXTRN	?_w2@MEMORY@@UEAAXPEAEKG@Z:PROC	; MEMORY::_w2
EXTRN	?_w1@MEMORY@@UEAAXPEAEKE@Z:PROC	; MEMORY::_w1
EXTRN	?_r4@MEMORY@@UEAAKPEAEK@Z:PROC	; MEMORY::_r4
EXTRN	?_r2@MEMORY@@UEAAGPEAEK@Z:PROC	; MEMORY::_r2
EXTRN	?_r1@MEMORY@@UEAAEPEAEK@Z:PROC	; MEMORY::_r1

 というような関数をexternしている部分がある。この関数名はコンパイラによって決定される物で、release/debugに左右されないが、ビルドターゲットには左右される名前である。そして命名規則はコンパイラ内部で決まっているので、このリスティングファイルを見て決めるしか今の所解決策は無い(かっこ悪い方法であるが)

 で、とりあえずWin32の方のアセンブラソースを書くと・・・・(ちなみにx86.asmというファイル名)
;x86
;	Class	MEMORY
;		_r1/_r2/_r4/_w1/_w2/_w4

	.686P
	.XMM
	.model	flat
	.code

PUBLIC	?_r1@MEMORY@@UAEEPAEK@Z
?_r1@MEMORY@@UAEEPAEK@Z	PROC
	mov	eax,dword ptr [esp+04h]
	mov	ecx,dword ptr [esp+08h]
	mov	 al, byte ptr [eax+ecx]
	ret	8
?_r1@MEMORY@@UAEEPAEK@Z	ENDP

PUBLIC	?_r2@MEMORY@@UAEGPAEK@Z
?_r2@MEMORY@@UAEGPAEK@Z	PROC
	mov	eax,dword ptr [esp+04h]
	mov	ecx,dword ptr [esp+08h]
	mov	 ax, word ptr [eax+ecx]
	ror	 ax,8
	ret	8
?_r2@MEMORY@@UAEGPAEK@Z	ENDP

PUBLIC	?_r4@MEMORY@@UAEKPAEK@Z
?_r4@MEMORY@@UAEKPAEK@Z	PROC
	mov	eax,dword ptr [esp+04h]
	mov	ecx,dword ptr [esp+08h]
	mov	eax,dword ptr [eax+ecx]
	bswap	eax
	ret	8
?_r4@MEMORY@@UAEKPAEK@Z	ENDP



PUBLIC	?_w1@MEMORY@@UAEXPAEKE@Z
?_w1@MEMORY@@UAEXPAEKE@Z	PROC
	mov	 dl, byte ptr [esp+0ch]	;data
	mov	eax,dword ptr [esp+04h]	;addr
	mov	ecx,dword ptr [esp+08h]	;offset
	mov	 byte ptr [eax+ecx], dl
	ret	0ch
?_w1@MEMORY@@UAEXPAEKE@Z	ENDP

PUBLIC	?_w2@MEMORY@@UAEXPAEKG@Z
?_w2@MEMORY@@UAEXPAEKG@Z	PROC
	mov	 dx, word ptr [esp+0ch]	;data
	mov	eax,dword ptr [esp+04h]	;addr
	mov	ecx,dword ptr [esp+08h]	;offset
	ror	dx,8
	mov	 word ptr [eax+ecx], dx
	ret	0ch
?_w2@MEMORY@@UAEXPAEKG@Z	ENDP

PUBLIC	?_w4@MEMORY@@UAEXPAEKK@Z
?_w4@MEMORY@@UAEXPAEKK@Z	PROC
	mov	edx,dword ptr [esp+0ch]	;data
	mov	eax,dword ptr [esp+04h]	;addr
	mov	ecx,dword ptr [esp+08h]	;offset
	bswap	edx
	mov	dword ptr [eax+ecx],edx
	ret	0ch
?_w4@MEMORY@@UAEXPAEKK@Z	ENDP

end

 まぁこれ自体は以前にインラインアセンブラで書いたのを、そのままアセンブラソースにしただけのものなので詳細な説明は省く。注釈としては関数のパラメータがスタックに積まれる事くらいか

 次にAMD64向けのアセンブラソースは・・・・(a64.asmというファイル名にした)
050315 ソースが未検証でthisポインタを全く考慮してませんでした(汗)修正しました
;AMD64
;	Class	MEMORY
;		_r1/_r2/_r4/_w1/_w2/_w4
;
;	char	MEMORY::_r1(char *addr,long offset)
;	short	MEMORY::_r2(char *addr,long offset)
;	long	MEMORY::_r4(char *addr,long offset)
;	void	MEMORY::_w1(char *addr,long offset,char  data)
;	void	MEMORY::_w2(char *addr,long offset,short data)
;	void	MEMORY::_w4(char *addr,long offset,long  data)
;
;		rcx	thisポインタ
;		rdx	第一引数
;		r8	第二引数
;		r9	第三引数
;		rax	返値



_TEXT	SEGMENT

PUBLIC	?_r1@MEMORY@@UEAAEPEAEK@Z
?_r1@MEMORY@@UEAAEPEAEK@Z	PROC
	mov	al,byte ptr [rdx+r8]
	ret	0
?_r1@MEMORY@@UEAAEPEAEK@Z	ENDP

PUBLIC	?_r2@MEMORY@@UEAAGPEAEK@Z
?_r2@MEMORY@@UEAAGPEAEK@Z	PROC
	mov	ax,word ptr [rdx+r8]
	ror	ax,8
	ret	0
?_r2@MEMORY@@UEAAGPEAEK@Z	ENDP

PUBLIC	?_r4@MEMORY@@UEAAKPEAEK@Z
?_r4@MEMORY@@UEAAKPEAEK@Z	PROC
	mov	eax,dword ptr [rdx+r8]
	bswap	eax
	ret	0
?_r4@MEMORY@@UEAAKPEAEK@Z	ENDP



PUBLIC	?_w1@MEMORY@@UEAAXPEAEKE@Z
?_w1@MEMORY@@UEAAXPEAEKE@Z	PROC
	mov	byte ptr [rdx+r8],r9b
	ret	0
?_w1@MEMORY@@UEAAXPEAEKE@Z	ENDP

PUBLIC	?_w2@MEMORY@@UEAAXPEAEKG@Z
?_w2@MEMORY@@UEAAXPEAEKG@Z	PROC
	ror	r9w,8
	mov	word ptr [rdx+r8],r9w
	ret	0
?_w2@MEMORY@@UEAAXPEAEKG@Z	ENDP

PUBLIC	?_w4@MEMORY@@UEAAXPEAEKK@Z
?_w4@MEMORY@@UEAAXPEAEKK@Z	PROC
	bswap	r9d
	mov	dword ptr [rdx+r8],r9d
	ret	0
?_w4@MEMORY@@UEAAXPEAEKK@Z	ENDP



_TEXT	ENDS

END

 ファイルの先頭の注釈にも書いてあるが、AMD64の場合関数のパラメーターはrcx,rdx,r8,r9の順に積まれ、後はスタックに積まれていく。今回は三つまでしかパラメータは無いので、全てのパラメータはレジスタ割付となる。
 x86のコードと比べてみて欲しい。もともとx86のコードも、インラインアセンブラの時からnakid関数としてプロローグ/エピローグコードを省きかなり小さいコードであったと思うが、AMD64はパラメータをレジスタで渡す為、全くスタックを使わずに済んでいる(もっともこの処理が簡単だからたまたまそうなっているだけだが)
 参考までにメモリアクセスの修飾はbyte/word/dword/qword ptrである。

 とまぁこのような感じでそれぞれのターゲットのアセンブラファイルを作成したら、それぞれプロジェクトに追加して次の様に設定する
AMD64コマンドラインml64 -c -Zi "-Fl$(IntDir)/device/memory/$(InputName).lst" "-Fo$(IntDir)/device/memory/$(InputName).obj" "$(InputPath)"
Win32コマンドラインml -c -Zi "-Fl$(IntDir)/device/memory/$(InputName).lst" "-Fo$(IntDir)/device/memory/$(InputName).obj" "$(InputPath)"

 説明/出力ファイルは適宜設定(VS8のページでも書いてるが、大規模になればなるほどちゃんと設定しておかないとダメ)
ポイントとしては、ビルドターゲットでないファイルは「ビルドから除外」を選択しておく必要があると言う事(AMD64の時はx86.asmを、Win32の時はa64.asmを)

 以上で問題が無ければコンパイル>リンクされると思う。