INC命令実装
これで準備が整いましたのでINC命令を実装します。この命令は、指定したレジスタの値を1増加(インクリメント)させます。VB.NETにも同じ役割を持った演算子がありますので知っている方も多いと思います。
この命令は一見簡単そうに見えますが、命令長が今までより少ないので十分な注意が必要です。これ以降はその点に注意しながら読み進めてください。
始めは何時も通りCommandName
列挙体にINCを追加してから、SearchOpeCodeMap
メソッドにINC命令に関する処理を追加します。
'行と列から命令を割り出す Select Case row Case 4 Select Case col Case 0 result.Name = CommandName.Inc result.Destination = RegisterName.EAX Case 1 result.Name = CommandName.Inc result.Destination = RegisterName.ECX Case 2 result.Name = CommandName.Inc result.Destination = RegisterName.EDX Case 3 result.Name = CommandName.Inc result.Destination = RegisterName.EBX Case Else Throw New ArgumentException("行" & row & "列" & col & "には対応しておりません。") End Select
この処理については特に説明の必要がないと思いますので話しを進めます。 次はSearchOpeCodeMap
メソッドの逆の処理をするGetBinary
メソッドを修正します。
'オペコードマップの行と列の値を決定 Select Case cmd Case CommandName.Inc result = 4 << 4 Select Case reg Case RegisterName.EAX Case RegisterName.ECX result += 1 Case RegisterName.EDX result += 2 Case RegisterName.EBX result += 3 Case Else Throw New ArgumentException("命令" & cmd.ToString() & "は" & _ reg.ToString() & "レジスタをサポートしておりません。") End Select
このメソッドの変更点についても説明の必要がないと思いますのでさらに話しを進めます。
次はINC命令を実行するためにIntelCPU
クラスの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.Inc Inc(cmd) Case CommandName.Mov Mov(cmd) End Select End While End Sub
ここまでは簡単な手順でしたが次は少し難しくなります。INC命令をInc
メソッドとGetIncValue
メソッドの2つのメソッドとして実装します。今までは一命令一メソッドでしたが、INC命令の対応するレジスタ数が多く、同じ処理が多くなりますので、共通部分を別のメソッドとして実装します。この方がコードが読みやすくなります。
これから筆者の実装例を掲載しますが、皆さまも一度自分で実装してみてください。ヒントはADD命令です。ADD命令の実装で分からない部分がある時は『VB.NETで仮想CPUを作ろう (7) - ADD命令実装』を一読してください。
'Inc命令の実装 Private Sub Inc(ByVal ope As OpeCode) Select Case ope.Destination Case RegisterName.EAX Me.EAX = Me.GetIncValue(Me.EAX) Case RegisterName.EBX Me.EBX = Me.GetIncValue(Me.EBX) Case RegisterName.ECX Me.ECX = Me.GetIncValue(Me.ECX) Case RegisterName.EDX Me.EDX = Me.GetIncValue(Me.EDX) End Select End Sub 'インクリメントして最終値を返す Private Function GetIncValue(ByVal value As UInteger) As UInteger Dim tmp As ULong = value + CULng(1) If UInteger.MaxValue >= tmp Then Me.CF = False value = tmp Else '桁あふれ(オーバーフロー)が発生 Me.CF = True '上位ビットを消す tmp = tmp And UInteger.MaxValue '計算結果を設定 value = CUInt(tmp) End If Return value End Function
これでINC命令の実装作業は終わりです。次はDEC命令を実装します。
DEC命令実装
この命令は、指定したレジスタの値を1減少(デクリメント)させます。VB.NETにも同じ役割を持った演算子がありますので知っている方も多いと思います。
この命令も一見簡単そうに見えますが、減算するのでINC命令よりも処理が複雑です。『VB.NETで仮想CPUを作ろう (8) - SUB命令実装』を理解する必要があります。分からない部分が出たら参考にしてください。
DEC命令を実装する手順はINC命令と同じなので省略し、Dec
メソッドに関する部分だけを掲載します。INC命令の実装を参考にぜひ皆さまも挑戦してください。どうしても分からない場合は記事に添付されているサンプルコードを参照してください。
'Dnc命令の実装 Private Sub Dec(ByVal ope As OpeCode) Select Case ope.Destination Case RegisterName.EAX Me.EAX = Me.GetDecValue(Me.EAX) Case RegisterName.EBX Me.EBX = Me.GetDecValue(Me.EBX) Case RegisterName.ECX Me.ECX = Me.GetDecValue(Me.ECX) Case RegisterName.EDX Me.EDX = Me.GetDecValue(Me.EDX) End Select End Sub 'デクリメントして最終値を返す Private Function GetDecValue(ByVal value As UInteger) As UInteger Dim tmp As Long = value - CLng(1) If 0 <= tmp Then Me.CF = False Me.SF = False value = tmp Else '桁あふれ(アンダーフロー)が発生 Me.CF = True Me.SF = True 'ビット反転させる Dim pmt As UInteger = CUInt(CUInt(tmp * -1) Xor UInteger.MaxValue) '計算結果を設定 value = CUInt(pmt) + 1 End If Return value End Function
後はINC命令とDEC命令の命令長が1なので、それを設定するように、GetCommandLength
メソッドを修正すればIntelCPU
クラス側の準備は終わります。
'命令長を取得します Public Shared Function GetCommandLength(ByVal cmd As CommandName) As Integer If cmd = CommandName.Inc OrElse cmd = CommandName.Dec Then Return 1 End If Return 2 End Function
これでINC命令とDEC命令の両方の実装が終わりましたので、テストを行うためにテストドライバ側を変更を行います。