GetBinaryメソッドの修正
修正部分のコードを抜粋します。
'オペコードマップの行と列の値を決定 Select Case cmd Case CommandName.Add Select Case reg Case RegisterName.AL result = 4 Case RegisterName.EAX result = 5 Case Else Throw New ArgumentException("命令" & cmd.ToString() & "は" & _ reg.ToString() & "レジスタをサポートしておりません。") End Select
ADD命令とは違って算術左シフトが必要な点に注意して下さい。それ以外は特に問題ないと思います。
Subtractメソッドの実装
Subtract
メソッドを実装する前にSUB命令の説明をします。SUB命令は引き算をする命令なのでADD命令と大差はありません。しかし、SUB命令は「マイナス値になる可能性がある」点が異なります。IntelのCPUはマイナス値をCFフラグレジスタ
とSFフラグレジスタ
の状態で表現しています。ですから、まずはIntelCpu
クラスに次のプロパティを追加します。
'符号を表すフラグ。 '1ならばマイナス、0ならばプラスを表している。 Private m_sf As Boolean = False Public Property SF() As Boolean Get Return Me.m_sf End Get Private Set(ByVal value As Boolean) Me.m_sf = value Me.OnFlagValueChanged(FlagName.SF, value) End Set End Property
このコードについては説明不要だと思いますので、話を先に進めます。2進数の引き算をまだ説明していなかったので、実装する前に簡単な計算例を挙げます。
例:3-10の場合
3は二進法表記で11、10は二進法表記で1010。この2つの数値で減算する際には、まず「10をビット反転させてから1を加えます。
1010⇒0101(ビット反転) 0101+1=0110
これを2の補数と呼びます。 次に3と先ほどの計算結果を足します。
0011 + 0110 = 1001
この時10000のようにオーバーフローしたらプラス値、しなかったらマイナス値とみなされます。 今回の様にマイナス値を10進数へ変換する場合は2の補数を求めます。
1001⇒0110 0110+1=0111
0111は7ですので答えが合っています。減算を加算で行うのは不思議だと思いますが、このように正しく導出されます。コンピューターの内部では減算も加算で行うそうです。その理由は、回路を単純化することにより、集積度を上げることが可能になるからです。 2進数の減算法をマスターしたらSUB命令実装の準備は終わりです。 これから筆者の実装例を提示しますが、一度自分で実装を試みて下さい。
'SUB(Subtract)命令の実装 Private Sub Subtract(ByVal ope As OpeCode) Select Case ope.Destination Case RegisterName.AL Dim tmp As Short = Me.AL - CShort(ope.Value.ValueLowByte) If 0 <= tmp Then Me.CF = False Me.SF = False Me.AL = tmp Else '桁あふれ(アンダーフロー)が発生 Me.CF = True Me.SF = True 'ビット反転させる Dim pmt As Byte = CByte(CByte(tmp * -1) Xor Byte.MaxValue) '計算結果を設定 Me.EAX = CUInt(pmt) + 1 End If Case RegisterName.EAX Dim tmp As Long = Me.EAX - CLng(ope.Value.ValueU32) If 0 <= tmp Then Me.CF = False Me.SF = False Me.EAX = tmp Else '桁あふれ(アンダーフロー)が発生 Me.CF = True Me.SF = True 'ビット反転させる Dim pmt As UInteger = CUInt(CUInt(tmp * -1) Xor UInteger.MaxValue) '計算結果を設定 Me.EAX = CUInt(pmt) + 1 End If End Select End Sub
ExecuteCommandメソッドの修正
前回もそうでしたが、このメソッドの修正はすごく簡単ですのでコードのみを掲載し、説明を省きます。
'解析済みの命令を実行します。 Public Sub ExecuteCommand() Dim cmd As OpeCode While cmds.Count <> 0 cmd = cmds.Dequeue() Select Case cmd.Name '命令を選択して実行 Case CommandName.Add Add(cmd) Case CommandName.Subtract Subtract(cmd) Case CommandName.Mov Mov(cmd) End Select End While End Sub
テストドライバの修正
テストドライバ側でSUBに対応するには、まずはSUB命令が指定できるようにCmdCombo
コントロールのItems
プロパティにSUB
を追加します。次に計算結果を確認するためにRegisterValueChanged
メソッドへ処理を追加します。一番単純に実装する場合は下記に示すように簡単です。
Case CommandName.Add If Me.RegisterCombo.SelectedIndex = RegisterName.EAX Then ResultLabel.Text = e.BeforeValue.ValueU32.ToString() & _ " + " & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueU32.ToString() Else : Me.RegisterCombo.SelectedIndex = RegisterName.AL ResultLabel.Text = e.BeforeValue.ValueLowByte.ToString() & _ " + " & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueLowByte.ToString() End If Case CommandName.Subtract If Me.RegisterCombo.SelectedIndex = RegisterName.EAX Then ResultLabel.Text = e.BeforeValue.ValueU32.ToString() & _ " - " & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueU32.ToString() Else : Me.RegisterCombo.SelectedIndex = RegisterName.AL ResultLabel.Text = e.BeforeValue.ValueLowByte.ToString() & _ " - " & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueLowByte.ToString() End If
しかし、この実装は実践では使用されません。なぜならば“重複部分が多すぎる”からです。重複部分が多いのにも関わらず、このように別にコードを書いてしまうと、表示フォーマットを変えようとした時に手順が煩雑となり、間違いが生じやすくなります。ですから、開発現場では下記のように実装します。
Case CommandName.Add, CommandName.Subtract Dim sign As String = "" If CmdCombo.SelectedIndex = CommandName.Add Then sign = " + " ElseIf CmdCombo.SelectedIndex = CommandName.Subtract Then sign = " - " End If If Me.RegisterCombo.SelectedIndex = RegisterName.EAX Then ResultLabel.Text = e.BeforeValue.ValueU32.ToString() & _ sign & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueU32.ToString() Else : Me.RegisterCombo.SelectedIndex = RegisterName.AL ResultLabel.Text = e.BeforeValue.ValueLowByte.ToString() & _ sign & _ UInteger.Parse(ValueText.Text).ToString("N", Me.format) & _ " = " & e.AfterValue.ValueLowByte.ToString() End If
ポイントはADD命令とSUB命令の違いが符号だけということです。それならば符号を変数に格納し、その部分だけを変更して表示すればいいということです。初心者の方は前の単純な実装例とこちらの実装例を見比べて下さい。コード量が減り、変更が行いやすくなったことが分かると思います。
まとめ
いかがでしょうか? 今回はより実践を意識して書きました。リファクタリングのノウハウや実装時の考え方などが伝われば幸いです。
それと、SUB命令の実装作業を通じてCPUの特色が感じられたことと思います。CPUは基礎的な計算法から人間とは違います。しかし、その計算法も人間が考えたものです。ですからその計算法を活用して、普段我々が行う10進数の計算でも10の補数を使用することにより、足し算で引き算を行うことができます。そういった、知的な楽しみをバイナリプログラミングを通じて感じていただければ幸いです。
次回はインクリメントとデクリメント命令の実装を通じて、各々長さが違う機械語をどのように扱えばいいのかを解説します。お楽しみに。