アセンブリ言語アセンブリ言語(アセンブリげんご、英: assembly language、アセンブリ)はビット列命令に対応した文字列命令を利用する低水準プログラミング言語の総称である[1]。 アセンブラ(英: Assembler)またはアセンブラ言語(英: Assembler Language)とも呼ばれる[注 1][2]。 概要プロセッサは機械語プログラムを直接読み取り実行する。しかし人間にとってビット列は直観的に理解しづらいため、機械語コーディングは容易でない。これを解決するために、ビット列に対応する文字列命令(ニーモニック)を利用するプログラミング言語の総称がアセンブリ言語である[1]。 アセンブリ言語を用いることで、機械語相当の低水準なコードをより直観的に記述できる。高度なアセンブリ言語ではアセンブラに対する命令(疑似命令)やマクロを用いて、より抽象的な記述が可能である[注 2]。パイプライン処理などを最適化するために命令順序を入れ替えたり、ラベルの位置関係によってアドレッシングモードを最適化するアセンブラもあり、必ずしもソーステキストの記述とアセンブルの結果が直接対応するとは限らない。 アセンブリ言語は機械語と強く結びついているため、各プロセッサ向けに仕様の異なる様々な(具体的な)アセンブリ言語が存在する(「アセンブリ言語」は総称である)。同じ命令セットに対しても複数のアセンブリ言語が存在しうる(例: GNUアセンブラのgasのインテルプロセッサ用)。 アセンブリ言語の基本文法として、1つの命令は1つのニーモニックと0個以上のオペランドからなる。プログラム全体はニーモニック/オペランド列、ディレクティブや擬似命令と呼ばれるメタな文、コメント、データで構成されている。通常の文はオペコードのニーモニックで始まり、パラメータ(データ、引数)のリストがそれに続く[3]。多くのアセンブリ言語はオペランドのアドレスや定数をラベル・シンボルで記述できハードコーディングを避けられる。 基本文法アセンブラの開発者によって用語の使い方に大きな差異があり、文の分類などが異なる。例えば、マシンのニーモニックや拡張ニーモニック以外は全て擬似命令と呼ぶ場合もある。典型的なアセンブリ言語は、プログラムの操作の定義に使われる命令文をニーモニック、データセクション、アセンブリディレクティブの3種類に分類する。 ニーモニックニーモニック(英: mnemonic)は処理内容に応じて各機械語命令に与えられた文字列・命令語である[4]。機械語のオペコードに相当する。 ビット列である機械語はその処理が直観的にわからないため、機械語コーディングは容易でない。人間がより容易に機械語と同等なコードを書くため、ビット列を意味ある文字列で表現するニーモニックが発明された[4]。例えばX64機械語 拡張ニーモニックは命令の特殊な用途をサポートするのに使われることが多く、本来の命令の名称からはその用途が連想できないときに使うことが多い。例えば、多くのCPUは明示的にNOP命令を用意していないが、その用途に使える命令は存在する。8086ではxchg ax,axという命令がnopとして使えるので、アセンブリ言語でnopを記述すると xchg ax,ax という命令に変換される。逆アセンブラにもこのあたりを認識し、xchg ax,axをnopに変換するものがある。同様にIBMのSystem/360とSystem/370のアセンブラでは、拡張ニーモニックNOPとNOPRを使用し、それぞれBCとBCRのマスク0の命令に変換する。SPARCアーキテクチャでは、拡張ニーモニックをsynthetic instructionsと呼んでいる[5]。 命令は一般に「オペコード」と0以上の「オペランド」で構成される。多くの命令は1つまたは2つの値を参照する。オペランドには即値(命令内に置かれる値)、レジスタ(暗黙のうちに使用される場合もある)、記憶装置内のデータの位置を示すアドレスなどがある。「拡張ニーモニック」はオペコードと特定オペランドの組合せを表すのに使われることが多い。例えば、System/360では、BC命令にマスク15を組み合わせたものがB、BC命令にマスク0を組み合わせたものがNOPという拡張ニーモニックで表される。オペランドの順序(例: ソースとディスティネーションの前後)は言語に依る。 オペランドオペランド(英: operand、被演算子)は命令の対象・引数である。1つの命令では、ニーモニックに続き0個以上のオペランドが記述される。オペランドにはソースとデスティネーションの二種類があり、データとして読み取られるのがソースで、オペコードで示された命令の実行結果が格納されるのがデスティネーションである。ソースには定数・レジスタ・メモリのいずれか、デスティネーションにはレジスタ・メモリのいずれかを指定する。 データセクションデータと変数を保持するデータ要素を定義するのに使われる命令文がある。データの型、長さ、境界(アライメント)を定義する。また、そのデータがプログラム外部(別ファイルでアセンブルされたプログラム)からも利用可能なのか、それともデータセクションを定義したプログラム内でのみ使用可能なのかも定義できる。一部のアセンブラはこれを擬似命令に分類している。 アセンブリディレクティブ→「ディレクティブ」も参照
アセンブリディレクティブは、擬似命令とも呼ばれ、アセンブラがアセンブリ実施中に実行すべき命令となっている[6]。プログラマが入力するパラメータによって、異なった形でアセンブルが行われるよう指示することができる。また、プログラムの見た目を操作して、可読性と保守性を向上させるのにも使われる。例えば、記憶装置の領域を予約し、その初期内容を指定するディレクティブなどがある。ディレクティブの名称はドットで始まることが多く、それによって通常のニーモニックと区別している。 擬似オペコード(pseudo-opcode)と言った場合、オブジェクトコードを実際に生成するディレクティブのみを指すこともある[7]。 ラベル/シンボルシンボリックアセンブラでは、任意の名前(ラベルまたはシンボル)とメモリ位置を対応付けることができる。通常、定数や変数に名前をつけることができ、命令文ではそれらの位置を名前で参照できる。実行コードではサブルーチンのエントリポイントと名前を関連付け、サブルーチンを名前で呼び出すことができる。サブルーチン内では、分岐命令の分岐先をラベルで示すことができる。一部のアセンブラは「ローカルシンボル」をサポートしており、通常のシンボルとは語彙的に区別する(例えば、"10$"を分岐先に使用する、など)。 一部のアセンブラは柔軟なシンボル管理を提供しており、複数の名前空間を管理したり、データ構造内のオフセットを自動的に計算したり、リテラル値やアセンブラが実施した単純な計算結果を参照するラベルを割り当てたりすることができる。ラベルは定数や変数をリロケータブルなアドレスで初期化するのにも使える。 例x86/IA-32プロセッサにおいて8ビット即値をレジスタに入れる命令を例にとる。 この命令のバイナリコードは 10110 で、その後に3ビットのレジスタを指定する識別子が続く。AL レジスタの識別子は 000 なので、次に示す機械語は AL レジスタに 01100001 というデータをロードする[8]。 10110000 01100001 このバイナリコードを人間が読みやすいように十六進法で表現すると次のようになる。 B0 61 ここで、 MOV AL, 61h ; Load AL with 97 decimal (61 hex)
この場合、定数61Hがソース、レジスタALがデスティネーションに該当し、命令が実行されると、定数61Hが、レジスタALに単純に格納される。これが人間にとってはさらに読みやすく覚えやすい。 前述のインテルの MOV のようにデータの転送の多くを同一の命令あるいはニーモニックとする場合もあれば、データのコピー/移動の方向などによって別々の命令あるいはニーモニックとする場合もある(「メモリからレジスタへの移動」を L、「レジスタからメモリへの移動」を ST、「レジスタからレジスタへの移動」を LR、「即値をメモリへ移動」を MVI など)。(この段落では命令セットの設計の話とアセンブリ言語の話を一緒にしている) インテルのオペコード 10110000( MOV AL, 1h ; Load AL with immediate value 1
MOV CL, 2h ; Load CL with immediate value 2
MOV DL, 3h ; Load DL with immediate value 3
MOVの構文には次の例のようにさらに複雑なものもある[9]。 MOV EAX, [EBX] ; Move the 4 bytes in memory at the address contained in EBX into EAX
MOV [ESI+EAX], CL ; Move the contents of CL into the byte at address ESI+EAX
MOVというニーモニックを使った文は、その内容によってアセンブラが88-8E、A0-A3、B0-B8、C6、C7のいずれかのオペコードに変換するので、プログラマはオペコードを知る必要がないし、オペコードを覚える必要もない[8]。 高級言語との違いアセンブリ言語は低水準プログラミング言語であり、C言語などの高級言語より抽象度が低い。すなわち言語機能(構文や型)が少ない。次の表は「基本的なアセンブリ言語」と高級言語の間にある言語機能差である。
この差はあくまで言語機能の差である。「高級言語でのみ可能、アセンブリ言語では不可」という意味ではない。例えばアセンブリ言語に関数構文は存在しないが関数に相当するパターンが存在する(関数プロローグ・エピローグ)。より正確な言い方をすれば、アセンブラで頻出するパターンを1つの機能として言語仕様に組み込んで抽象度を上げていった言語が高級言語である。 高水準文法より抽象化され少ないコード量でアセンブラを書くために様々な高水準文法がアセンブリ言語に導入されてきた。現在では高水準化のメインストリームは高級言語に移った一方[11]、目的に応じてアセンブリ言語を選択するユーザー向けに高機能なアセンブリ言語の開発も続いている[12]。 マクロ→「マクロ (コンピュータ用語)」も参照 アセンブリ言語においてもマクロが利用される。一般的なマクロと同様、高度なアセンブラマクロでは制御構文導入・引数展開・ユーザー定義マクロ適用などが可能である。文字列であるオペコード・ニーモニックはマクロの対象となるため、これを利用して疑似ニーモニックによる記述も可能になる。 例えば、一部のZ80用アセンブラでは、ld hl,bc というマクロ命令を ld l,c と ld h,b という2命令に展開する[13]。メインフレームの時代には、マクロは特定顧客の大規模ソフトウェアシステムのカスタマイズや、メーカーのオペレーティングシステムを顧客の要望に合わせた特注版にするのに使われていた。IBMの VM/CMS、リアルタイムトランザクション処理用アドオン、CICS、ACP/TPF[14]などで使われてきた。 制御構造構造化プログラミングの要素を取り入れたアセンブラもある。最初期には "Concept-14 macro set" がSystem/360のマクロアセンブラにIF/ELSE/ENDIFなどの制御構造を導入した[15][16]。また8080/Z80プロセッサ向けの A-natural ではブロック構造や命令実行順序の制御が採用された。 また構造化プログラミングとは若干異なるが、キャリーラボはBASIC風の文法のアセンブリ言語 BASE を開発した。Z80用のBASE-80とMC6809用のBASE-09がある。BASEの表記例は下記の通り(BASE-09)。 S[A,B,X,U
A=$80
A=A+$C0
S]A,B,X,U,PC
上記の記述は下記のアセンブラ表記に対応する。 PSHS A,B,X,U
LDA #$80
ADDA #$C0
PULS A,B,X,U,PC
アセンブラアセンブル(英: assemble)はアセンブリ言語で書かれたプログラムから機械語で書かれたオブジェクトコードへの変換である。具体的には、ニーモニックをオペコードに変換しシンボル名をメモリ位置や他の実体に変換する[6]。 アセンブルは比較的単純な規則からなるため、人の手でも実行できる(ハンドアセンブル)。単純な作業を効率良くミス無く行うのはプログラムの得意分野であり、そのようなソフトウェアが開発された。このアセンブリをおこなうプログラムをアセンブラ(英: assembler)という。初期にはアセンブリプログラムとも呼ばれた[17]。 シンボル名による参照の利用はアセンブラの重要な機能であり、面倒な計算やプログラム修正に伴うアドレスの更新の手間を省くことができる。また、オブジェクトコードを生成する際、ローダ用情報も併せて生成するアセンブラもある[18]。マクロを含むアセンブリ言語に対応している場合、処理系にはm4のような汎用プロセッサあるいはプロセッサ内蔵アセンブラ(マクロアセンブラ)が利用される[19]。ポリモーフィズム、継承[8]などをもつ高水準アセンブリ言語に対応したアセンブラは高水準アセンブラと呼ばれる[20]。 動作プラットフォーム以外のターゲットプラットフォームを選択できるアセンブラはクロスアセンブラとも呼ばれる(参考: クロスコンパイラ)。メタアセンブラは、アセンブリ言語の文法や意味論を記述したものを入力とし、その言語のためのアセンブラを出力するプログラムである[21]。 逆方向の変換、すなわちオブジェクトコードのアセンブリ言語化をおこなうプログラムを逆アセンブラという。 分類アセンブラは様々な観点から分類できる。パス回数(アセンブル時のソースファイル走査回数)の観点ではワンパスアセンブラとマルチパスアセンブラに分類できる。
どちらの場合も、アセンブラは最初のパスで各命令のサイズを確定させる必要があり、それによって後に出現するシンボルのアドレスを計算する。命令のサイズは後から定義されるオペランドの型や距離に依存することがあるため、アセンブラは最初のパスでは悲観的な見積もりをし、必要に応じてその後のパスまたは errata にて1つ以上のNOP命令(何もしない命令)を挿入してすき間を埋める必要がある。最適化を行うアセンブラでは、最初の悲観的コードをその後のパスで稠密なコードに書き換えてアドレスの再計算を行うことがある。 もともとワンパスアセンブラは高速であるためよく使われていた。マルチパス動作をするには、磁気テープを巻き戻したりパンチカードのデッキをセットし直して読み込む必要があったためである。現代のコンピュータではマルチパスであってもそのような遅延は生じない。マルチパスアセンブラは errata がないため、リンク処理(アセンブラが直接実行コードを生成する場合はローダの処理)が高速化される[22]。 主なアセンブラ
Unix系システムでは、アセンブラを as と呼ぶのが一般的だが、実体はそれぞれのOSで異なる。GNUアセンブラを使っているものが多い。 同じ系統のプロセッサであっても、複数のアセンブリ言語の方言が存在する。アセンブラによっては他の方言のアセンブリ言語も使用可能な場合がある。例えば、TASMはMASM用コードを入力として受け付け可能だが、逆は不可能である。FASMとNASMは文法がほぼ同じだが、サポートしているマクロが異なるため、相互の翻訳は困難である。いずれも基本機能は同じだが、追加機能に差異がある[23]。 歴史アセンブリ言語は、ごく単純なものまで含めれば、プログラム内蔵方式のコンピュータの最初期の1940年代から存在している。世界で最初に実用的に稼働したノイマン型電子計算機とされるEDSAC (1949) の initial orders(現代の用語ではブートローダーに相当するもの)は、テープにパンチされた十進によるアドレスを、内部表現の二進に変換するなどの機能を持っていた(命令については、「1文字のニーモニック」に見えるかもしれないが、それは実際には同機の機械語そのものである)[24]。ナサニエル・ロチェスターは1954年に IBM 701 用アセンブラを書いている。1955年、Stan Poley が IBM 650 用言語アセンブリSOAP (Symbolic Optimal Assembly Program) を開発した[25]。 コンピュータの歴史の初期には、このような、プログラムによって機械語プログラムを生成することを自動プログラミングと呼んだ。 ドナルド・ギリースは、まだ発明されていなかったアセンブラを開発中に、フォン・ノイマンから開発を即座に止めるように言われた、という1950年代初期ならではの逸話がある。当時は、人間が手作業でもできるような瑣末な仕事をコンピュータにさせるような時代が来るとは考えられておらず、単に時間の無駄だとノイマンは考えたのである。 歴史的には多数のプログラム(OSやアプリケーション)がアセンブリ言語だけで書かれてきた。ALGOLの方言であるESPOLで書かれた Burroughs MCP (1961) が登場するまで、オペレーティングシステムはアセンブリ言語で書くのが普通だった。IBMのメインフレーム用ソフトウェアの多くはアセンブリ言語で書かれていた。COBOL、FORTRAN、PL/I などが取って代わっていったが、1990年代になってもアセンブリ言語のコードベースを保守し続けていた大企業も少なくない。 初期のマイクロコンピュータでも同様に広く用いられた。これは、リソースの制約が厳しく、メモリやディスプレイのアーキテクチャが特殊だったからである。また、マイクロコンピュータ向けの高水準言語のコンパイラがなかったという面も重要である。また、初期のマイクロコンピュータのユーザは趣味としての使用が主であり、何でも自前で作るという精神もそれに影響していたと見られる。 1980年代から1990年代にかけて、ホームコンピュータ(ZX Spectrum、コモドール64、Amiga、Atari ST など)でもアセンブリ言語がよく使われていた。というのもそれらのBASICは性能が低く、ハードウェアの全機能を利用できないことが多かったためである。例えば、Amigaにはフリーウェアのアセンブリ言語統合開発環境 ASM-One assembler があり、Microsoft Visual Studio に匹敵する機能を備えていた。 Don French が開発した VIC-20 用アセンブラは 1,639 バイトという小ささで、世界一小さいアセンブラと言われている。アドレスをシンボルで表現でき、各種アドレス計算(四則演算、AND、OR、冪乗など)が可能だった[26]。 1980年代のビジネスソフトでは、例えば表計算ソフト Lotus 1-2-3 などはアセンブリ言語で書かれていた。日本では松などが該当[27]する。 1990年代に入っても、コンシューマーゲームの多くはアセンブリ言語でプログラムが書かれていた。しかしゲーム内容が複雑化し、プログラムの規模が増大するにつれて、アセンブラでは開発が困難となり、高水準言語による開発が主流となっていった。例えばプレイステーションではGCCが公式のSDKに含まれていて、標準の開発言語はC言語であった[28][29]。この時代のゲーム機は3次元コンピュータグラフィックスの積極的な導入が始まっており、ハードウェア性能も向上したことから、C言語による開発も十分可能となったが、コンパイラの最適化能力が未成熟だったこともあいまって、ハードウェア性能を最大限引き出すにはアセンブリ言語を駆使した手動最適化や細かなチューニングが必要となることも多かった。セガサターンの最高性能を引き出してプレイステーションに対抗するには、アセンブリ言語を使うしかなかったと述べていた業界関係者もいた[30]。ただし一方で、ファミコン時代すでにメタルスレイダーグローリーやスーパーファミコンのMOTHER 2・シムシティ[31]、プレイステーションのクラッシュ・バンディクーで[32]、開発の一部にLISPが使われていたという話もあり、当時のコンシューマーゲームの分野ではアセンブリ言語やC言語が全てだったというわけではない。 2000年代初頭、マイクロソフトは原始的なプログラマブルシェーダーに対応したDirectX (Direct3D) 8.0をリリースした。このDirect3D 8.0におけるシェーダープログラムは、グラフィックスハードウェアに依存しない中間言語(バイトコード)を出力することのできるアセンブリ言語(シェーダーアセンブラ)を使用して記述するものだった。2001年には世界で初めてプログラマブルシェーダーに対応したコンシューマーゲーム機として初代Xboxが登場したが、このXboxに搭載されていたグラフィックスAPIもDirect3D 8.x相当のカスタマイズ版[33]であり、CPU上で実行するホストプログラム(ゲームアプリケーション本体のコード)はC++を使って記述する一方、GPU上で実行するシェーダープログラムの記述にはアセンブラを使用していた。のちにHLSLやCg (C for Graphics) といった高水準シェーディング言語が開発され、HLSLに対応したDirect3D 9.0以降はシェーダープログラムも高水準言語を利用して記述するようになった。Direct3D 10のシェーダーモデル4.0以降は、シェーダーアセンブラではなくHLSLの使用が必須となっている[34]。 現在の最適化コンパイラは人手で書かれたアセンブリ言語のコードと同等の性能を発揮すると言われている[35](例外もある[36][37][38])。最近[いつ?]のプロセッサやメモリサブシステムは複雑化してきたため、コンパイラでもアセンブリ言語でも効果的な最適化がますます困難になってきている[39][40]。さらにプロセッサが高性能化し律速が入出力やページングへ移ることで、コーディングが性能向上に貢献するケースは以前より少なくなっている。 一方C++やC#のような、Cよりもさらに高水準の言語が主流になってからも、コンパイラが出力したアセンブリコードを解析して最適化やチューニングの余地を探るといった手法は一般的に行なわれている[41]。 利用低水準言語であるアセンブラはC言語などの高級言語と異なる領域で利用される。 目的アセンブラを用いる目的として以下が挙げられる。
事例アセンブリ言語が用いられる事例として以下が挙げられる。
なお一方で、最近[いつ?]のコンピュータの命令セットはその多くはどれも似ている。したがって、どれか1つのアセンブリ言語を学ぶだけで、基本概念、どんなときにアセンブリ言語を使用するのが適しているか、高水準言語から効率的な実行コードを生成する方法をある程度は学習できる[44]。 高水準言語との連携
脚注注釈
出典
参考文献
関連項目外部リンク
|