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命令を実装する予定です。この命令も手強いので読み応えがあると思います。お楽しみに。