はじめに
本記事はVB.NETの初歩的な記法だけを使って、簡単な機械語で動く仮想CPUの実装法を解説します(※CPUにもいろいろありますが、この記事ではIntel社が製造しているCPUを対象とします)。その過程を通じて、初心者でもバイナリプログラミングが楽しめることと、バイナリプログラミングの魅力を伝えたいと思っています。
今回はSUB命令の実装を通じてCPUの理解を深めます。
これまでの連載
- VB.NETで仮想CPUを作ろう
- VB.NETで仮想CPUを作ろう (2) - レジスタの実装
- VB.NETで仮想CPUを作ろう (3) - 仮想CPUのGUI化
- VB.NETで仮想CPUを作ろう (4) - テストドライバの改良
- VB.NETで仮想CPUを作ろう (5) - CPUの基礎動作の実装
- VB.NETで仮想CPUを作ろう (6) - MOV命令実装
- VB.NETで仮想CPUを作ろう (7) - ADD命令実装
下準備
今回は前回の実装を拡張していきますので、あらかじめ前回までの部分の実装を済ませておいてください。後は専門用語とCPU構造の確認のため、前回用意した3つのIntel社のマニュアルをすぐ読める状態にしてください。
それに加えて、中巻 B: 命令セット・リファレンス N-ZのA-6とA-7ページを印刷しておくことをお勧めします。この表をあらかじめ印刷しておき、その紙を参照しながら実装作業をすると効率がよくなります。
前置き
前回ADD命令の実装を行った際、テストしやすいように、テストドライバの下部に計算式が表示されるようにしました。しかし、このために、TestForm_Load
メソッド、CmdExeButton_Click
メソッド、RegisterValueChanged
メソッドの、計3つものメソッドにまたがって実装を行いました。
このように実装がいくつものメソッドに分かれると、コードの可読性を下げ、コードの保守性を下げる結果を招いてしまいます。コードの関連性が見えにくくなりますし、他のコードに混ざると論理的な一貫性がなくなり、人間には理解しにくいものとなるからです。
まさしくリファクタリングを実施するべき状況といえます。
そこで、SUB命令を実装する前にリファクタリングを実施します。実際の開発現場でも、この考えは有効で、元のコードに何らかの機能を付け加える際には、もっとプログラムを簡潔に表現できないか確認するとよいでしょう。
リファクタリングー分析
リファクタリングする際は、何のコードを実装するのか改めて考え、その目的をなるべく簡単に一か所にまとめて実装できないかを検討します。今回の目的は、「計算式の結果を確認する」ことです。これを簡単かつ一か所で実装するために、それを阻害している要因を既存コードを分析して発見します。以下に実装部分を抜粋します。
Private cpu As IntelCpu Private format As NumberFormatInfo Private before As Register
'レジスタの初期値を保存
EaxValueLabel.Tag = EaxValueLabel.Text
AlValueLabel.Tag = Me.before.ValueLowByte.ToString()
'情報欄を更新する Select Case CmdCombo.SelectedIndex Case CommandName.Mov If RegisterCombo.SelectedIndex = RegisterName.EAX Then ResultLabel.Text = "値が" & EaxValueLabel.Text & "に初期化されました。" EaxValueLabel.Tag = EaxValueLabel.Text AlValueLabel.Tag = Me.before.ValueLowByte.ToString() ElseIf RegisterCombo.SelectedIndex = RegisterName.AL Then ResultLabel.Text = "値が" & AlValueLabel.Text & "に初期化されました。" AlValueLabel.Tag = AlValueLabel.Text End If Case CommandName.Add If RegisterCombo.SelectedIndex = RegisterName.EAX Then ResultLabel.Text = EaxValueLabel.Tag.ToString() & _ " + " & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & EaxValueLabel.Text EaxValueLabel.Tag = EaxValueLabel.Text AlValueLabel.Tag = Me.before.ValueLowByte.ToString() ElseIf RegisterCombo.SelectedIndex = RegisterName.AL Then ResultLabel.Text = AlValueLabel.Tag.ToString() & _ " + " & _ UInteger.Parse(ValueText.Text).ToString() & _ " = " & AlValueLabel.Text AlValueLabel.Tag = AlValueLabel.Text End If End Select
'レジスタの初期値を保存
EaxValueLabel.Tag = EaxValueLabel.Text
AlValueLabel.Tag = Me.before.ValueLowByte.ToString()
このように実装を抜粋すると、コードが散らばっていることが一目瞭然です。この様な状況では、まずフィールドをみます。その理由は、フィールドやプロパティを使用しないと、メソッドをまたがって情報をやり取りできないからです。もちろん絶対にできないわけではありませんが、コードがこのように複数のメソッドに散らばっている場合はまず疑うべきです。
フィールドをみると、before
が一番今回の実装では影響が大きそうです。before
は、「前方、~に先立って」などの意味を持つ単語ですから「変更前の数値」を保存していることが推測できます。
試しにbefore
を検索するか、Microsoft Visual Studioなどの開発環境で作業している人は、before
を選択して右クリックし、[すべての参照の検索]を選択して、このフィールドがどこで使われているか確認してください。TestForm_Load
メソッド、CmdExeButton_Click
メソッド、RegisterValueChanged
メソッドの、3つのメソッドにまたがっていることが確認できます。
これでbefore
が「変更前の数値」を保存していることが確認できましたので、その意味を考えてみます。そもそも、この情報はIntelCpu
オブジェクトを利用するオブジェクトが持つべきものなのでしょうか? 設計思想は人それぞれですが筆者は違うと思います。それは、IntelCpu
オブジェクトを利用するオブジェクトに負担を押し付けるということは、それだけコード量が増え、エラーが入り込む余地を与える結果に繋がるからです。
今回はサンプルということもあり、テストドライバからしかIntelCpu
オブジェクトは使用されていません。しかし、今後このオブジェクトを利用することを考えると、それらすべてのオブジェクトに余計な負担を強いることは賢明だとは思えません。そうするよりも、少し手間をかけてIntelCpu
オブジェクトを使いやすく工夫する方が労力は減り、余計なエラーの発生を防げます。この考え方に基づき、「変更前の値」の情報はIntelCpu
オブジェクトが持つことにしました。