機械語とアセンブリ言語

マイクロプロセッサは、メモリからデータを読み込み、そのビット値の0,1の組み合わせ(ビットパターン)に応じて、あらかじめそのビットパターンに対応づけられている所定の処理を行う。 つまり、ビットパターンはマイクロプロセッサに行わせる処理を指定するものであり、 これを機械命令(machine instruction)、あるいは単に命令(instruction)と呼ぶ。 マイクロプロセッサに所望の処理を行わせるために様々な命令を必要な順序で組み合わせ並べたものが機械語(machine language)プログラムである。

命令は、処理内容を表すオペコード(operation code)と、処理対象データを表すオペランド(operand、被処理データ)からなる。
例えば、プロセッサ内のレジスタXに数値10を転送する機械語命令を考える。 レジスタ'X'と数値'10'のどちらもデータ転送という処理の処理対象であるが、通常はレジスタのようなマイクロプロセッサに固有のものを指し示す部分はオペコードの中に入れて「レジスタXへのデータ転送」という処理とし、'10'のように命令を使用するときに変化する部分のみがオペランドとされる。 つまり、「レジスタXへのデータ転送」という処理を示すオペコードと、Xに転送されるデータを示すオペランド(上の場合は'10')によって命令が表される。
なお、これはRISC型プロセッサとCISC型プロセッサでは異なり、CISC型であってもプロセッサの種類によって状況は異なっている。

通常マイクロプロセッサは、まずオペコードを読み込んで処理内容を認識し、もしもオペランドを必要とする処理であれば、次にオペランドを読み込む。
オペランドの処理方法は、命令によって異なる。 ある命令ではオペランドは数値として扱い、別の命令ではメモリアドレスとして扱う。 また別の命令ではオペランドをメモリアドレスとし、そのメモリアドレスの内容をメモリアドレスとしてその内容を演算する、といったことも考えられる。 このようにオペランドの扱いは、命令の処理内容次第である。

一般的なマイクロプロセッサでは、オペコードとオペランドは連続したメモリアドレスから読み込む。 そこで、オペコードとオペランドを併せた命令のバイト数によって1バイト命令、2バイト命令などというように命令を区別することがある。 1バイト命令の実行ではメモリから1バイトだけを読み込み、2バイト命令の実行では、メモリから2バイトを読み込む。

機械語プログラムは1と0の羅列であり、人間にとってあまりにも分かりにくいので、ビットパターンを4ビットずつに区切って16進数に直すことがあるが、これも機械語プログラムと呼ぶことがある。
しかし、'10001100'といったビットパターンやこれを16進数に直した'8C'などがどの命令を表すのかは、人間にはすぐには分からない。 そこで各命令に、人間にとって意味があり、その命令が行う処理を類推できる文字列を対応付ける。この文字列をニモニック(mnemonic)と呼ぶ。そしてニモニックによって表したプログラムをアセンブリ言語(assembly language)プログラムと呼ぶ。

アセンブリ言語と機械語は1対1に対応している。したがって、アセンブリ言語プログラムから機械語プログラムへの変換は単純な置換操作である。この変換を行うツールをアセンブラ(assembler)と呼ぶ。
BASIC、C、FORTRANといった高級プログラミング言語(high-level programming language)では、コンパイラ(compiler)によって高級言語プログラムを機械語プログラムに翻訳する。 そのため、優秀なコンパイラを使用することで、質の悪いプログラムにもコンパイラが最適化を施して優れた機械語プログラムに翻訳することもできる。 (ただし、アルゴリズム的に劣悪なプログラムでは、いくら最適化を施しても優れたアルゴリズムに基づくプログラムと対等になることはない)
一方、アセンブリ言語と機械語は1対1に対応しており、通常はアセンブラは最適化は行わない。 したがって、アセンブリ言語によるプログラミングの優劣は、そのまま機械語プログラムの実行速度や実行効率の優劣となる。

■アセンブリ言語プログラミングの文法

アセンブリ言語による命令記述書式は以下の通りである。
	[ラベル:] 命令ニモニック [オペランド]
ラベルは省略可能である。 またオペランドを必要とする命令では、オペランドを記述できる。 命令ニモニック中やオペランド中の','(カンマ)は、命令を見やすくするために使用しても構わないが、アセンブルの際に削除される。

・コメント

'#'(シャープ)から行末まではコメントとして処理される。アセンブルの際に、コメントは削除される。

・ラベル

数値として解釈されることのない文字列であり、アセンブリ言語プログラム中では、このラベルがつけられた命令のアドレスを表す。
全角文字および':'(コロン)、'#'(シャープ)は使用してはならない。 文字数に制限はないが、8文字以内を推奨する。
また、命令ニモニックやレジスタ名と同じ文字列をラベルに使用すべきではない。

・オペランド

オペランドには、ラベル、数値、括弧によって囲ったラベルや数値を指定できる。 オペランドを解析することで、オペランドから種別と値を抽出する。 オペランドの解析は、次の手順による。
  1. 括弧で囲われている場合は括弧を外す。
  2. 数値であるか調べ、数値であるならばオペランドの種別を'arg'とする。
  3. 数値でなければラベルを含むか調べ、ラベルを含むならばオペランドの種別を'label'とする。
  4. 数値でもラベルでもなければなにも変換を施さない。この場合はオペランドの文字列そのものがオペランドの種別であり、値はない。
  5. オペランドの種別に、最初に取り除いた括弧を付ける。
以下に、手順にしたがってオペランドの解析を述べる。

  1. 括弧の認識
    まず、オペランドの前後に括弧'('と')'があるか調べる。本アセンブラでは、二重括弧'((','))'まで認識する。この括弧は、オペランドの値という点では意味はないが、定義された命令書式とのパターンマッチにおいて重要である。
     例  
    元のオペランド 括弧を外したオペランド
    ($30) $30
    ((WORK)) WORK

  2. 文字の認識
    前後に'(ダッシュ、プライム、アポストロフィーなど)がある場合は、文字列データとみなし、'を取り除いた文字列の先頭の1文字の文字コード(8ビット整数)をオペランドの値とする。
     例  
    オペランド 種別
    'A' arg 65(文字'A'の文字コード)
    '12' arg 49(文字'1'の文字コード)
    ''' arg 39(文字'''の文字コード)
    '\' arg 92(文字'\'の文字コード)

  3. 数値の認識
    '0'~'9'までの数字が任意の文字数連なったもの、あるいは先頭の'$'(ドル)の後ろに'0'~'9', 'A', 'B', 'C', 'D', 'E', 'F'の16種類の文字(a~fは小文字も可)が任意の文字数連なったものである。 前者は10進数として変換され、後者は16進数として変換される。 なお、10進数の場合には、先頭に'-'(マイナス)がある場合には負の数となる。
    数値として認識された場合は、その数値がオペランドの値となる。
     例  
    オペランド 種別
    $30 arg 16進数(10進数の48)
    -123 arg 10進数

  4. ラベルの認識
    オペランドの中に、プログラム中で定義されているラベルの文字列が含まれているか調べる。 ラベルが含まれている場合は、そのラベルが付けられている命令が格納されるメモリアドレスがオペランドの値となる。
    なお、オペランドが、ラベル+数値ラベル-数値数値+ラベル数値-ラベルといった形式の場合は、ラベルのメモリアドレスと数値を加算あるいは減算した値をオペランドの値とする。
     例  
    オペランド 種別
    WORK label 100(ラベルのアドレス(10進数))
    WORK+1 label 101(100+1)
    WORK-$10 label 84(100-16)
    5+WORK label 105(5+100)
    (ただし、プログラム中でWORKがラベルとして定義されており、そのアドレスが100(10進数)の場合)

最初に取り除いた括弧が種別に戻されることを考慮して、最終的にオペランドには以下のような種別が与えられる。

 例  
オペランド 与えられる種別 オペランドの値
'a' arg 97(文字'a'の文字コード)
(' ') (arg) 32(文字' 'の文字コード)
$30 arg $30(10進数の48)
($30) (arg) $30(10進数の48)
WORK label 100
(($30)) ((arg)) $30(10進数の48)
(WORK+5) (label) 105
X X 0
(Y) (Y) 0
ただし、ラベルWORKが定義されており、アドレスは100の場合である。 また、最後の2つの例は、数値でもラベルでもない場合である。

・疑似命令

実際にマイクロプロセッサが実行する命令ではないが、アセンブラに指示をあたえるための疑似命令が存在する。

db疑似命令とdw疑似命令は、初期値をもったデータの記述、あるいはプログラムの作業領域の確保に利用できる。

以下の例では、メモリアドレス$40(10進数の64)に2バイトのデータ保存領域(ラベルはWORK)とメモリアドレス$42(10進数の66)に1バイトのデータ保存領域(ラベルはWORK1)を確保している。

 例  
		org $40
	WORK:	dw 0
	WORK1:	db 0


■アセンブラの動作

本アセンブラは、3パス構成となっている。

命令の認識

命令の認識は、以下の手順で行われる。
  1. 前処理
    プログラムから1行を読み込み、コメントを表す'#'から行末までを削除する。 ':'(コロン)より前は、この行に付けられたラベルであるとして処理し、行頭から':'(コロン)までおよび直後の空白を削除する。 その後、行の中の','(カンマ)をすべて空白に置換する。

    例えば、読み込んだ1行が

      WORK: LD AC, -25    # ACに-25を代入
    
    であるとする。まず、コメントを削除すると以下のようになる。
      WORK: LD AC, -25
    
    ここで、':'が存在するので':'より前、すなわち'WORK'はこの行のラベルである。 この行のラベルが'WORK'であることをアセンブラ内部に記録して、行からラベルを削除すると以下のようになる。
      LD AC, -25
    
    ','を空白に置換して
      LD AC  -25
    
    となる。

  2. 命令書式の認識
    前処理を施した行データを、空白を区切りとして単語に分割する(ただし、オペランドが文字データである場合に文字データ中の空白は区切りとはならない)。 ここの段階では、各単語が、オペランドであるか命令ニモニックであるか区別できない。 そこで、全ての単語をオペランドとして解析を行い、抽出した種別によって置換する。 この結果、オペランドならば抽出された正しい種別に置換され、命令ニモニックならば置換の前後で変化はない。
    その後、各単語を間に空白をはさんで連結し、命令定義ファイル中で定義されている命令の命令書式とパターンマッチングを行う。 パターンマッチングが成功すれば、アセンブル完了である。パターンマッチングが失敗すれば、その命令は未定義であるとみなす。

    例えば、前処理後の行データが

      LD AC  -25
    
    であるとき、空白によって区切ると以下の3つの単語
      'LD' 'AC' '-25'
    
    に分割される。プログラム中に'LD', 'AC'といったラベルがなければ'LD'と'AC'は変更なくそのままである。 '-25'は10進数であるので、オペランドとして認識され、種別を表す'arg'に置換される。 すなわち、上の3つの単語はそれぞれ
      'LD' 'AC' 'arg'
    
    に置換される。これを空白をはさんで連結すると
      'LD AC arg'
    
    となる。この文字列'LD AC arg'を命令定義ファイルによって定義された命令の書式と比較する。 ここでは、前述の命令'LD AC, arg'にマッチする(命令書式中の','(カンマ)は無視し、連続する複数の空白は単一の空白に置換されることに注意)。 命令'LD AC, arg'は2バイト命令と定義されているので、読み込んだ命令は、オペコード'10001100'の2バイト命令であり、オペランドは十進数の'-25'と翻訳される。

    注意
    命令のニモニックである'LD'やレジスタを指す'AC'といった特別な意味をもつ単語と同じ文字列をラベルとして使用してはならない。 本来ラベルとして使用してはならない文字列をラベルとして使用してもアセンブラ自体のエラーとはならないが、該当する命令がない旨のエラーになる。

命令コードの書き込み

第2パスにおける命令コードの書き込みは以下のように行う。

  1. まず、命令格納メモリアドレスレジスタARを用意し、値を0とする。
  2. 命令を認識するとともにオペランドの数値を求める。
  3. 命令が疑似命令でなければ、ARが指すメモリアドレス位置にオペコードを書き込む。 オペランドがあれば、メモリアドレス位置AR+1にオペランドの数値を書き込む。 その後、命令のバイト数をARに加算する。
    org疑似命令の場合は、org疑似命令のオペランドの値をARに代入する。
    db疑似命令の場合はオペランドの1バイト、dw疑似命令の場合はオペランドの2バイトをARが指すメモリアドレス位置に書き込み、ARをそれぞれ1ないし2だけ増加する。
  4. プログラム中の全ての行について2., 3.を繰り返す。