アセンブラ言語とは?
コンピュータの言葉に最も近いプログラミング言語です。
アセンブラ言語は、コンピュータが直接理解できる機械語を、人間がより理解しやすい形で表現したプログラミング言語です。機械語は0と1の組み合わせで構成された非常に低レベルな言語ですが、アセンブラ言語では、これらの機械語に対応するニーモニックと呼ばれる覚えやすい単語や記号を用いてプログラムを記述します。
自己紹介
ところで、君は誰だい?
私は、20世紀からプログラミングをしてきた者で、ファミコンやスーパーファミコンなどいろいろなコンシューマゲーム機向けのゲームソフトを開発してきました。
iモードが始まる頃、IT業界に転職して今はフロントやバックエンドなど様々なプログラミングして開発を行ってきました。
ITエンジニアになってからは、アセンブラ言語を使用することはなくなりましたが、私としては楽しさと苦しさを共に暮らしてきた、無二の親友みたいなものです。
なお、この記事には注意点があります。
今回、記憶から取り出してきたので、間違いなどがあるかもしれません、
制限の厳しい世界
昔のアセンブラ言語というか、昔のCPU(Central Processing Unit)は、非常に低機能でやれることが非常に少ないです。
- レジスタが少ない
- CPUで計算するには、まずCPU内部のレジスタ(メモリー)に入れる必要があるが、汎用レジスタが少なく、複雑な演算を行うには工夫が必要でした
- 命令セットの制限
- 命令の種類が少なく、複雑な処理を行うには多くの命令を組み合わせる必要がありました
- クロック速度の低さ
- 現代のCPUに比べると処理速度が非常に遅いです
乗算と除算
昔のCPUは、乗算と除算がありません。
普通に考えると8倍するときは、乗算で行います。
2 * 8 = 16
それでは、乗算がなければ、しょうがないので加算を使用します。
2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 = 16
しかし、これだと非常に非効率ですし、そもそも7回も加算命令を行うため遅いので使えません。
それでは、どうするのか?
シフト演算を使用します。
シフト演算とは、コンピュータの内部でデータを構成するビットを、左または右に一定数だけずらす演算のことです。まるで数字の桁をずらすように、ビット列全体をずらすことで、効率的に乗算や除算、ビットの操作を行うことができます。
2進数にすると理解しやすいので2進数で書いてみます。
2は00000010と表記します。
これを、8倍だと8回シフトするのではなく、2倍は左に1回、4倍は左に2回、8倍は左に3回シフトします。
00000010
↓ 2倍
00000100
↓ 4倍
00001000
↓ 8倍
00010000
00010000を10進数にすると16になります。
加算だと7回演算しましたが、シフト演算なら3回で済みましたので、4回お得です。
これで、乗算を使わなくてもできましたね!
実際のコードで書くとこんな感じです。
LDA #2 ; Aレジスタに2を代入
LDX #3 ; Xレジスタに繰り返し回数を設定
loop:
ASL A ; Aレジスタを左シフト
DEX ; Xレジスタをデクリメント
BNE loop ; Xが0になるまでループ
じゃあ、10倍は?
正解は、4倍して元の数値を加算すれば良いですね。
00000010
↓ 2倍
00000100
↓ 4倍
00001000
↓ 元の数値を加算
00001010
00001010は10進数にすると10になります。
実際のコードで書くとこんな感じです。
LDA #2 ; Aレジスタに2を代入
LDX #2 ; Xレジスタに繰り返し回数を設定
loop:
ASL A ; Aレジスタを左シフト
DEX ; Xレジスタをデクリメント
BNE loop ; Xが0になるまでループ
ADC #2 ; 2を足す
除算も同じです。
16を1/8にするには右シフトをします。
16を1/10にするには、先に減算してから右シフトをします。
乗算と除算程度でも、ちょっとした頭の体操になります。
高速化
高速化は楽しい
昔のCPUはシンプルな設計ゆえに、普通にプログラムを書いても高速化しませんが、巧みなプログラミング技法やハードウェアの工夫によって、性能を向上させることができます。
下に代表的な例を2つあげます。
* ループ展開
* インデックスインダイレクトモード
ループ展開
伝統技能
伝統的な技としてはループ展開があります。
先ほど紹介した2を8倍するコードをループ展開してみます。
元のコード
LDA #2 ; Aレジスタに2を代入
LDX #3 ; Xレジスタに繰り返し回数を設定
loop:
ASL A ; Aレジスタを左シフト
DEX ; Xレジスタをデクリメント
BNE loop ; Xが0になるまでループ
ループ展開したコード
LDA #2 ; Aレジスタに2を代入
ASL A ; Aレジスタを左シフト
ASL A ; Aレジスタを左シフト
ASL A ; Aレジスタを左シフト
元のコードは5つのニーモニックですが、ループするので11命令が実行されます。
ループ展開すると、4つのニーモニックで済むので、約1/3の命令で実行できます。
ループ展開は簡単に効果が出るので、本当によく使う技なのですが、やり過ぎには注意で、容量制限があるところでは、簡単に容量いっぱいになってしまいます。
インデックスインダイレクトモード
アドレッシングモードは奥が深いです
古いCPUには、アドレッシングモードというモードがあり、それによって同じ命令でもアドレスの指定方法が違ってたり奥が深いです。
下に代表的なアドレッシングモードを列挙します。
- アブソリュート
- ゼロページ
- リレティブ
- インデックスアブソリュート
- インデックスゼロページ
- インダイレクト
- インデックスインダイレクト
- インダイレクトインデックス
このようなモードを吟味して、これを組み合わせて速度を追求するのです、とくに「インデックスインダイレクト」は非常に使い勝手がよく、多くの人が、これを利用してプログラミンをしておりました。
こちらがインデックスインダイレクトモードのコードです。
LDA ($20,X)
これは何をしているのかを説明します。
仮にXレジスタに4という値を入れておきます。
そうなると、インデックスとXレジスタを演算したあと、ゼロページアドレス$24
と$25
の内容を参照します。
例えば、このような状態となっているとします。
$24: $10
$25: $80
この命令は、以下の手順を実行します。
- X レジスタから4を取り出しインデックス
$20
を加算しゼロページアドレス$24
を生成します - 下位バイトの
$24
から$10
を取得する - 上位バイトの
$25
から$80
を取得する - 上位バイトと下位バイトを組み合わせてアドレス
$8010
を生成します - 最後に、メモリー
$8010
にある値をアキュムレータにロードします
インダイレクトモードのLDA #$8010
と大差ないように思われますが、パラメーターを使用できるのが、インデックスインダイレクトモードのメリットです。
また、インダイレクトモードはニーモニックと合わせて3バイトですが、インデックスインダイレクトモードはニーモニックと合わせても2バイトで済みます。
高機能の上に、速度も早いというので、本当によく使用したのを覚えています。
最後に
完全に老害のポエムでしたが、昔の苦労が少しわかっていただけたでしょうか?
他にもテクニックはたくさんあるのですが、使用頻度が高いけど簡単なのを掲載しました。
みなさんも、古いCPUで遊んでみてはいかがでしょうか?