MUL命令の実装
MUL命令は命令フォーマットだけではなく、動作そのものも注意するべき点がいくつかあるので、まずはそれを説明してからMUL命令の実装の解説を行います。
MUL命令は掛け算を行う一見単純な命令ですが、CPUにとって重要なことがあります。それは「桁あふれ」です。人間は最大ビット数がないのでその気になれば、紙と鉛筆で何桁でも計算できますが、CPUは数値を記憶できる範囲が限られています。分かりにくいと思いますので実例を挙げます。
例えば、ALの値が255の時、BL(値は2)をMUL命令で計算したらCPUは困ってしまいます。なぜかと言うと、ALは1バイトまでの数値(0~255)しか記憶できないので510をALに格納できないのです。ですからMUL命令は、桁あふれが起こった場合、違うレジスタに値を格納することになっています。先ほどの例で言うと、510は2進数で111111110なので、下位の8ビット11111110をALに、余った1ビット1をAHに代入することになっています。従って先ほどの答えはAL=254、AH=1となります。なお、ダブルワードのレジスタの計算結果が桁あふれした場合は、下位32ビットをEAXに、残りの上位ビットをEDXに格納することになっています。この動作を念頭にMUL命令の実装をしてください。筆者の実装例を掲載します。
'MUL命令の実装 Private Sub Mul(ByVal ope As OpeCode) Select Case ope.BitCount Case 8 '掛け算をする Dim value As UShort = Me.AL If ope.IsHi Then value *= ope.Value.ValueHighByte Else value *= ope.Value.ValueLowByte End If '結果をレジスタに反映する If value <= Byte.MaxValue Then Me.CF = False Me.SF = False Me.AL = value Else '上半分をAHに下半分をALに代入する Me.CF = True Me.SF = True Me.AX = value End If Case 32 '掛け算をする Dim value As ULong = CULng(Me.EAX) * CULng(ope.Value.ValueU32) '結果をレジスタに反映する If value <= UInteger.MaxValue Then Me.CF = False Me.SF = False Me.EAX = value Else '上半分をEAXに下半分をEDXに代入する Me.CF = True Me.SF = True '下半分 Me.EAX = value And CULng(UInteger.MaxValue) '上半分 Dim msk As ULong = ULong.MaxValue - CULng(UInteger.MaxValue) value = value And msk Me.EDX = CUInt(value >> 32) End If End Select End Sub
一番難しいと思われる「上半分をEAXに下半分をEDXに代入する」の部分を解説します。
先ほど説明したように計算結果が32ビットを超えると、下位32ビットをEAXに、上位ビットをEDXに設定せねばなりません。それを実現するためには、ビットごとの論理積を使います。下位32ビットを取り出すにはUInteger.MaxValueと結果でビットごとの論理積を行います。上位ビットを取り出すには、結果を64ビットとして(32ビット*32ビットは最大64ビットなため)扱い、上位32ビットが1・下位32ビットが0の値とビットごとの論理積を計算します。
ここで注意しなくてはならないのは、この時点での結果は64ビットだということです。ですから最後に32ビット右算術シフトをして32ビットの値へと変換しています。この処理が分かれば、他の処理も分かると思います。
これでMUL命令の実装ができましたので、MUL命令を実行するためにExecuteCommandメソッドの修正を行います。
ExecuteCommandメソッドの修正
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.Dec Dec(cmd) Case CommandName.Mov Mov(cmd) Case CommandName.Mul 'この部分を追加 Mul(cmd) End Select End While End Sub
これでIntelCpu側ですべきことは全て終わりましたので、次項ではテストドライバ側の修正を解説します。
テストドライバ側の修正
まず始めにすることはMUL命令が選択できるようにCmdComboコントロールのItemsプロパティの値にMulを追加することです。追加し終わったら、CmdExeButton_Clickメソッドを修正します。これから筆者の実装を掲載しますので参考にしてください。
'指定された機械語を実行する Private Sub CmdExeButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CmdExeButton.Click Dim binary(1) As Byte '指定された命令を実行する If Me.GetCmmandAndRegisterBinay(binary) = False Then Return End If '命令長を取得 Dim length As Integer = IntelCpu.GetCommandLength(Me.CmdCombo.SelectedIndex) '命令長が1の場合は値に関するバイナリは必要ない If length > 1 Then If Me.CmdCombo.SelectedIndex = CommandName.Mul Then 'ModR/Mのバイナリを取得する binary(1) = IntelCpu.GetModRMBinary(CommandName.Mul, _ CType(Me.RegisterCombo.SelectedIndex, RegisterName)) Me.ExecuteCommand(binary) Else If Me.GetValueBinary(binary) = True Then Me.ExecuteCommand(binary) End If End If Else Me.ExecuteCommand(binary) End If End Sub
「ModR/Mのバイナリを取得する」の部分に注目してください。今までは単純にGetValueBinaryメソッドを呼び出して即値のバイナリを取得していましたが、MUL命令ではModeR/Mのバイナリが必要なので、GetModRMBinaryメソッドを呼び出しています。ModeR/Mは即値とは違って、必ず1バイトであることに注意してください。
これで一応MUL命令のテストが行えますが、MUL命令が桁あふれを起こした際に混乱を生みますので、CFレジスタとSFレジスタの値が常に確認できるようにした方が良いでしょう。具体的に言いますと、CFレジスタとSFレジスタの変更イベントを作り、その通知をテストドライバに受け取り、その結果をコントロールで表示するようにします。サンプルプログラムを参考にぜひ実装してみてください。
まとめ
いかがでしょうか? 今回はMUL命令の実装を通じて、ソースオペランドでレジスタを指定する方法を解説しました。今回で初めて、CPUにレジスタが複数あることの意味が見えてきたかと思います。次回はDIV命令を実装する予定です。この命令も手強いので読み応えがあると思います。お楽しみに。
