SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

VB.NETで学ぶ機械語の基礎

VB.NETで仮想CPUを作ろう (8) - SUB命令実装

VB.NETで学ぶ機械語の基礎 第8回

  • X ポスト
  • このエントリーをはてなブックマークに追加

GetBinaryメソッドの修正

 修正部分のコードを抜粋します。

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クラスに次のプロパティを追加します。

GetBinaryメソッドの修正部分の抜粋
'符号を表すフラグ。
'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命令の実装例
'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メソッドの修正

 前回もそうでしたが、このメソッドの修正はすごく簡単ですのでコードのみを掲載し、説明を省きます。

SUB命令の実装例
'解析済みの命令を実行します。
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メソッドへ処理を追加します。一番単純に実装する場合は下記に示すように簡単です。

もっとも簡単なSUB命令への対応の抜粋
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

 しかし、この実装は実践では使用されません。なぜならば“重複部分が多すぎる”からです。重複部分が多いのにも関わらず、このように別にコードを書いてしまうと、表示フォーマットを変えようとした時に手順が煩雑となり、間違いが生じやすくなります。ですから、開発現場では下記のように実装します。

好ましいSUB命令への対応の抜粋
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の補数を使用することにより、足し算で引き算を行うことができます。そういった、知的な楽しみをバイナリプログラミングを通じて感じていただければ幸いです。

 次回はインクリメントとデクリメント命令の実装を通じて、各々長さが違う機械語をどのように扱えばいいのかを解説します。お楽しみに。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
VB.NETで学ぶ機械語の基礎連載記事一覧

もっと読む

この記事の著者

インドリ(インドリ)

分析・設計・実装なんでもありのフリーエンジニア。ブログ「無差別に技術をついばむ鳥(http://indori.blog32.fc2.com/)」の作者です。アドバイザーをしたり、システム開発したり、情報処理技術を研究したりと色々しています。座右の銘は温故知新で、新旧関係なく必要だと考えたものは全て学...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2946 2010/04/27 12:02

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング