sh4.rb   [plain text]


# Copyright (C) 2013 Apple Inc. All rights reserved.
# Copyright (C) 2013 Cisco Systems, Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS, INC. ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS, INC. OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

require 'risc'

class Node
    def sh4SingleHi
        doubleOperand = sh4Operand
        raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^dr/
        "fr" + ($~.post_match.to_i).to_s
    end
    def sh4SingleLo
        doubleOperand = sh4Operand
        raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^dr/
        "fr" + ($~.post_match.to_i + 1).to_s
    end
end

class SpecialRegister < NoChildren
    def sh4Operand
        @name
    end

    def dump
        @name
    end

    def register?
        true
    end
end

SH4_TMP_GPRS = [ SpecialRegister.new("r3"), SpecialRegister.new("r11"), SpecialRegister.new("r13") ]
SH4_TMP_FPRS = [ SpecialRegister.new("dr10") ]

class RegisterID
    def sh4Operand
        case name
        when "a0"
            "r4"
        when "a1"
            "r5"
        when "t0"
            "r0"
        when "t1"
            "r1"
        when "t2"
            "r2"
        when "t3"
            "r10"
        when "t4"
            "r6"
        when "cfr"
            "r14"
        when "sp"
            "r15"
        when "lr"
            "pr"
        else
            raise "Bad register #{name} for SH4 at #{codeOriginString}"
        end
    end
end

class FPRegisterID
    def sh4Operand
        case name
        when "ft0", "fr"
            "dr0"
        when "ft1"
            "dr2"
        when "ft2"
            "dr4"
        when "ft3"
            "dr6"
        when "ft4"
            "dr8"
        when "fa0"
            "dr12"
        else
            raise "Bad register #{name} for SH4 at #{codeOriginString}"
        end
    end
end

class Immediate
    def sh4Operand
        raise "Invalid immediate #{value} at #{codeOriginString}" if value < -128 or value > 127
        "##{value}"
    end
end

class Address
    def sh4Operand
        raise "Bad offset #{offset.value} at #{codeOriginString}" if offset.value < 0 or offset.value > 60
        if offset.value == 0
            "@#{base.sh4Operand}"
        else
            "@(#{offset.value}, #{base.sh4Operand})"
        end
    end

    def sh4OperandPostInc
        raise "Bad offset #{offset.value} for post inc at #{codeOriginString}" unless offset.value == 0
        "@#{base.sh4Operand}+"
    end

    def sh4OperandPreDec
        raise "Bad offset #{offset.value} for pre dec at #{codeOriginString}" unless offset.value == 0
        "@-#{base.sh4Operand}"
    end
end

class BaseIndex
    def sh4Operand
        raise "Unconverted base index at #{codeOriginString}"
    end
end

class AbsoluteAddress
    def sh4Operand
        raise "Unconverted absolute address at #{codeOriginString}"
    end
end


#
# Lowering of shift ops for SH4. For example:
#
# rshifti foo, bar
#
# becomes:
#
# negi foo, tmp
# shad tmp, bar
#

def sh4LowerShiftOps(list)
    newList = []
    list.each {
        | node |
        if node.is_a? Instruction
            case node.opcode
            when "ulshifti", "ulshiftp", "urshifti", "urshiftp", "lshifti", "lshiftp", "rshifti", "rshiftp"
                if node.opcode[0, 1] == "u"
                    type = "l"
                    direction = node.opcode[1, 1]
                else
                    type = "a"
                    direction = node.opcode[0, 1]
                end
                if node.operands[0].is_a? Immediate
                    maskedImm = Immediate.new(node.operands[0].codeOrigin, node.operands[0].value & 31)
                    if maskedImm.value == 0
                        # There is nothing to do here.
                    elsif maskedImm.value == 1 or (type == "l" and [2, 8, 16].include? maskedImm.value)
                        newList << Instruction.new(node.codeOrigin, "sh#{type}#{direction}x", [maskedImm, node.operands[1]])
                    else
                        tmp = Tmp.new(node.codeOrigin, :gpr)
                        if direction == "l"
                            newList << Instruction.new(node.codeOrigin, "move", [maskedImm, tmp])
                        else
                            newList << Instruction.new(node.codeOrigin, "move", [Immediate.new(node.operands[0].codeOrigin, -1 * maskedImm.value), tmp])
                        end
                        newList << Instruction.new(node.codeOrigin, "sh#{type}d", [tmp, node.operands[1]])
                    end
                else
                    tmp = Tmp.new(node.codeOrigin, :gpr)
                    newList << Instruction.new(node.codeOrigin, "move", [Immediate.new(node.operands[0].codeOrigin, 31), tmp])
                    newList << Instruction.new(node.codeOrigin, "andi", [node.operands[0], tmp])
                    if direction == "r"
                        newList << Instruction.new(node.codeOrigin, "negi", [tmp, tmp])
                    end
                    newList << Instruction.new(node.codeOrigin, "sh#{type}d", [tmp, node.operands[1]])
                end
            else
                newList << node
            end
        else
            newList << node
        end
    }
    newList
end


#
# Lowering of simple branch ops for SH4. For example:
#
# baddis foo, bar, baz
#
# will become:
#
# addi foo, bar, tmp
# bs tmp, baz
#

def sh4LowerSimpleBranchOps(list)
    newList = []
    list.each {
        | node |
        if node.is_a? Instruction
            annotation = node.annotation
            case node.opcode
            when /^b(addi|subi|ori|addp)/
                op = $1
                bc = $~.post_match

                case op
                when "addi", "addp"
                    op = "addi"
                when "subi", "subp"
                    op = "subi"
                when "ori", "orp"
                    op = "ori"
                end

                if bc == "s"
                    raise "Invalid operands number (#{node.operands.size})" unless node.operands.size == 3
                    if node.operands[1].is_a? RegisterID or node.operands[1].is_a? SpecialRegister
                        newList << Instruction.new(node.codeOrigin, op, node.operands[0..1])
                        newList << Instruction.new(node.codeOrigin, "bs", node.operands[1..2])
                    else
                        tmpVal = Tmp.new(node.codeOrigin, :gpr)
                        tmpPtr = Tmp.new(node.codeOrigin, :gpr)
                        addr = Address.new(node.codeOrigin, tmpPtr, Immediate.new(node.codeOrigin, 0))
                        newList << Instruction.new(node.codeOrigin, "leap", [node.operands[1], tmpPtr])
                        newList << Instruction.new(node.codeOrigin, "loadi", [addr, tmpVal])
                        newList << Instruction.new(node.codeOrigin, op, [node.operands[0], tmpVal])
                        newList << Instruction.new(node.codeOrigin, "storei", [tmpVal, addr])
                        newList << Instruction.new(node.codeOrigin, "bs", [tmpVal, node.operands[2]])
                    end
                else
                    newList << node
                end
            when "bmulio", "bmulpo"
                raise "Invalid operands number (#{node.operands.size})" unless node.operands.size == 3
                tmp1 = Tmp.new(node.codeOrigin, :gpr)
                tmp2 = Tmp.new(node.codeOrigin, :gpr)
                newList << Instruction.new(node.codeOrigin, node.opcode, [tmp1, tmp2].concat(node.operands))
            else
                newList << node
            end
        else
            newList << node
        end
    }
    newList
end


#
# Lowering of double accesses for SH4. For example:
#
# loadd [foo, bar, 8], baz
#
# becomes:
#
# leap [foo, bar, 8], tmp
# loaddReversedAndIncrementAddress [tmp], baz
#

def sh4LowerDoubleAccesses(list)
    newList = []
    list.each {
        | node |
        if node.is_a? Instruction
            case node.opcode
            when "loadd"
                tmp = Tmp.new(codeOrigin, :gpr)
                addr = Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, 0))
                newList << Instruction.new(codeOrigin, "leap", [node.operands[0], tmp])
                newList << Instruction.new(node.codeOrigin, "loaddReversedAndIncrementAddress", [addr, node.operands[1]], node.annotation)
            when "stored"
                tmp = Tmp.new(codeOrigin, :gpr)
                addr = Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, 0))
                newList << Instruction.new(codeOrigin, "leap", [node.operands[1].withOffset(8), tmp])
                newList << Instruction.new(node.codeOrigin, "storedReversedAndDecrementAddress", [node.operands[0], addr], node.annotation)
            else
                newList << node
            end
        else
            newList << node
        end
    }
    newList
end


#
# Lowering of double specials for SH4.
#

def sh4LowerDoubleSpecials(list)
    newList = []
    list.each {
        | node |
        if node.is_a? Instruction
            case node.opcode
            when "bdltun", "bdgtun"
                # Handle specific floating point unordered opcodes.
                newList << Instruction.new(codeOrigin, "bdnan", [node.operands[0], node.operands[2]])
                newList << Instruction.new(codeOrigin, "bdnan", [node.operands[1], node.operands[2]])
                newList << Instruction.new(codeOrigin, node.opcode[0..-3], node.operands)
            when "bdnequn", "bdgtequn", "bdltequn"
                newList << Instruction.new(codeOrigin, node.opcode[0..-3], node.operands)
            when "bdneq", "bdgteq", "bdlteq"
                # Handle specific floating point ordered opcodes.
                outlabel = LocalLabel.unique("out_#{node.opcode}")
                outref = LocalLabelReference.new(codeOrigin, outlabel)
                newList << Instruction.new(codeOrigin, "bdnan", [node.operands[0], outref])
                newList << Instruction.new(codeOrigin, "bdnan", [node.operands[1], outref])
                newList << Instruction.new(codeOrigin, node.opcode, node.operands)
                newList << outlabel
            else
                newList << node
            end
        else
            newList << node
        end
    }
    newList
end


#
# Lowering of misplaced labels for SH4.
#

def sh4LowerMisplacedLabels(list)
    newList = []
    list.each {
        | node |
        if node.is_a? Instruction
            case node.opcode
            when "jmp"
                if node.operands[0].is_a? LabelReference
                    tmp = Tmp.new(codeOrigin, :gpr)
                    newList << Instruction.new(codeOrigin, "jmpf", [tmp, node.operands[0]])
                else
                    newList << node
                end
            when "call"
                if node.operands[0].is_a? LabelReference
                    tmp1 = Tmp.new(codeOrigin, :gpr)
                    tmp2 = Tmp.new(codeOrigin, :gpr)
                    newList << Instruction.new(codeOrigin, "callf", [tmp1, tmp2, node.operands[0]])
                else
                    newList << node
                end
            else
                newList << node
            end
        else
            newList << node
        end
    }
    newList
end


class Sequence
    def getModifiedListSH4
        result = @list

        # Verify that we will only see instructions and labels.
        result.each {
            | node |
            unless node.is_a? Instruction or
                    node.is_a? Label or
                    node.is_a? LocalLabel or
                    node.is_a? Skip
                raise "Unexpected #{node.inspect} at #{node.codeOrigin}"
            end
        }

        result = sh4LowerShiftOps(result)
        result = sh4LowerSimpleBranchOps(result)
        result = riscLowerMalformedAddresses(result) {
            | node, address |
            if address.is_a? Address
                case node.opcode
                when "btbz", "btbnz", "cbeq", "bbeq", "bbneq", "bbb", "loadb"
                    (0..15).include? address.offset.value and
                        ((node.operands[0].is_a? RegisterID and node.operands[0].sh4Operand == "r0") or
                         (node.operands[1].is_a? RegisterID and node.operands[1].sh4Operand == "r0"))
                when "loadh"
                    (0..30).include? address.offset.value and
                        ((node.operands[0].is_a? RegisterID and node.operands[0].sh4Operand == "r0") or
                         (node.operands[1].is_a? RegisterID and node.operands[1].sh4Operand == "r0"))
                else
                    (0..60).include? address.offset.value
                end
            else
                false
            end
        }
        result = sh4LowerDoubleAccesses(result)
        result = sh4LowerDoubleSpecials(result)
        result = riscLowerMisplacedImmediates(result, ["storeb", "storei", "storep", "muli", "mulp", "andi", "ori", "xori",
            "cbeq", "cieq", "cpeq", "cineq", "cpneq", "cib", "baddio", "bsubio", "bmulio", "baddis",
            "bbeq", "bbneq", "bbb", "bieq", "bpeq", "bineq", "bpneq", "bia", "bpa", "biaeq", "bpaeq", "bib", "bpb",
            "bigteq", "bpgteq", "bilt", "bplt", "bigt", "bpgt", "bilteq", "bplteq", "btiz", "btpz", "btinz", "btpnz", "btbz", "btbnz"])
        result = riscLowerMalformedImmediates(result, -128..127)
        result = sh4LowerMisplacedLabels(result)
        result = riscLowerMisplacedAddresses(result)

        result = assignRegistersToTemporaries(result, :gpr, SH4_TMP_GPRS)
        result = assignRegistersToTemporaries(result, :gpr, SH4_TMP_FPRS)

        return result
    end
end

def sh4Operands(operands)
    operands.map{|v| v.sh4Operand}.join(", ")
end

def emitSH4Load32(constant, dest)
    outlabel = LocalLabel.unique("load32out")
    constlabel = LocalLabel.unique("load32const")
    $asm.puts "mov.l #{LocalLabelReference.new(codeOrigin, constlabel).asmLabel}, #{dest.sh4Operand}"
    $asm.puts "bra #{LocalLabelReference.new(codeOrigin, outlabel).asmLabel}"
    $asm.puts "nop"
    $asm.puts ".balign 4"
    constlabel.lower("SH4")
    $asm.puts ".long #{constant}"
    outlabel.lower("SH4")
end

def emitSH4Load32AndJump(constant, scratch)
    constlabel = LocalLabel.unique("load32const")
    $asm.puts "mov.l #{LocalLabelReference.new(codeOrigin, constlabel).asmLabel}, #{scratch.sh4Operand}"
    $asm.puts "jmp @#{scratch.sh4Operand}"
    $asm.puts "nop"
    $asm.puts ".balign 4"
    constlabel.lower("SH4")
    $asm.puts ".long #{constant}"
end

def emitSH4LoadImm(operands)
    if operands[0].value == 0x40000000
        # FirstConstantRegisterIndex const is often used (0x40000000).
        # It's more efficient to "build" the value with 3 opcodes without branch.
        $asm.puts "mov #64, #{operands[1].sh4Operand}"
        $asm.puts "shll16 #{operands[1].sh4Operand}"
        $asm.puts "shll8 #{operands[1].sh4Operand}"
    elsif (-128..127).include? operands[0].value
        $asm.puts "mov #{sh4Operands(operands)}"
    elsif (-32768..32767).include? operands[0].value
        constlabel = LocalLabel.unique("loadconstant")
        $asm.puts "mov.w @(6, PC), #{operands[1].sh4Operand}"
        $asm.puts "bra #{LocalLabelReference.new(codeOrigin, constlabel).asmLabel}"
        $asm.puts "nop"
        $asm.puts ".word #{operands[0].value}"
        constlabel.lower("SH4")
    else
        emitSH4Load32(operands[0].value, operands[1])
    end
end

def emitSH4Branch(sh4opcode, operand)
    $asm.puts "#{sh4opcode} @#{operand.sh4Operand}"
    $asm.puts "nop"
end

def emitSH4ShiftImm(val, operand, direction)
    tmp = val
    while tmp > 0
        if tmp >= 16
            $asm.puts "shl#{direction}16 #{operand.sh4Operand}"
            tmp -= 16
        elsif tmp >= 8
            $asm.puts "shl#{direction}8 #{operand.sh4Operand}"
            tmp -= 8
        elsif tmp >= 2
            $asm.puts "shl#{direction}2 #{operand.sh4Operand}"
            tmp -= 2
        else
            $asm.puts "shl#{direction} #{operand.sh4Operand}"
            tmp -= 1
        end
    end
end

def emitSH4BranchIfT(label, neg)
    outlabel = LocalLabel.unique("branchIfT")
    sh4opcode = neg ? "bt" : "bf"
    $asm.puts "#{sh4opcode} #{LocalLabelReference.new(codeOrigin, outlabel).asmLabel}"
    if label.is_a? LocalLabelReference
        $asm.puts "bra #{label.asmLabel}"
        $asm.puts "nop"
    else
        emitSH4Load32AndJump(label.asmLabel, SH4_TMP_GPRS[0])
    end
    outlabel.lower("SH4")
end

def emitSH4IntCompare(cmpOpcode, operands)
    $asm.puts "cmp/#{cmpOpcode} #{sh4Operands([operands[1], operands[0]])}"
end

def emitSH4CondBranch(cmpOpcode, neg, operands)
    emitSH4IntCompare(cmpOpcode, operands)
    emitSH4BranchIfT(operands[2], neg)
end

def emitSH4CompareSet(cmpOpcode, neg, operands)
    emitSH4IntCompare(cmpOpcode, operands)
    if !neg
        $asm.puts "movt #{operands[2].sh4Operand}"
    else
        outlabel = LocalLabel.unique("compareSet")
        $asm.puts "mov #0, #{operands[2].sh4Operand}"
        $asm.puts "bt #{LocalLabelReference.new(codeOrigin, outlabel).asmLabel}"
        $asm.puts "mov #1, #{operands[2].sh4Operand}"
        outlabel.lower("SH4")
    end
end

def emitSH4BranchIfNaN(operands)
    raise "Invalid operands number (#{operands.size})" unless operands.size == 2
    $asm.puts "fcmp/eq #{sh4Operands([operands[0], operands[0]])}"
    $asm.puts "bf #{operands[1].asmLabel}"
end

def emitSH4DoubleCondBranch(cmpOpcode, neg, operands)
    if cmpOpcode == "lt"
        $asm.puts "fcmp/gt #{sh4Operands([operands[0], operands[1]])}"
    else
        $asm.puts "fcmp/#{cmpOpcode} #{sh4Operands([operands[1], operands[0]])}"
    end
    emitSH4BranchIfT(operands[2], neg)
end

class Instruction
    def lowerSH4
        $asm.comment codeOriginString
        case opcode
        when "addi", "addp"
            if operands.size == 3
                if operands[0].sh4Operand == operands[2].sh4Operand
                    $asm.puts "add #{sh4Operands([operands[1], operands[2]])}"
                elsif operands[1].sh4Operand == operands[2].sh4Operand
                    $asm.puts "add #{sh4Operands([operands[0], operands[2]])}"
                else
                    $asm.puts "mov #{sh4Operands([operands[0], operands[2]])}"
                    $asm.puts "add #{sh4Operands([operands[1], operands[2]])}"
                end
            else
                $asm.puts "add #{sh4Operands(operands)}"
            end
        when "subi", "subp"
            raise "#{opcode} with #{operands.size} operands is not handled yet" unless operands.size == 2
            if operands[0].is_a? Immediate
                $asm.puts "add #{sh4Operands([Immediate.new(codeOrigin, -1 * operands[0].value), operands[1]])}"
            else
                $asm.puts "sub #{sh4Operands(operands)}"
            end
        when "muli", "mulp"
            $asm.puts "mul.l #{sh4Operands(operands[0..1])}"
            $asm.puts "sts macl, #{operands[-1].sh4Operand}"
        when "negi", "negp"
            if operands.size == 2
                $asm.puts "neg #{sh4Operands(operands)}"
            else
                $asm.puts "neg #{sh4Operands([operands[0], operands[0]])}"
            end
        when "andi", "andp", "ori", "orp", "xori", "xorp"
            raise "#{opcode} with #{operands.size} operands is not handled yet" unless operands.size == 2
            sh4opcode = opcode[0..-2]
            $asm.puts "#{sh4opcode} #{sh4Operands(operands)}"
        when "shllx", "shlrx"
            raise "Unhandled parameters for opcode #{opcode}" unless operands[0].is_a? Immediate
            if operands[0].value == 1
                $asm.puts "shl#{opcode[3, 1]} #{operands[1].sh4Operand}"
            else
                $asm.puts "shl#{opcode[3, 1]}#{operands[0].value} #{operands[1].sh4Operand}"
            end
        when "shld", "shad"
            $asm.puts "#{opcode} #{sh4Operands(operands)}"
        when "loaddReversedAndIncrementAddress"
            # As we are little endian, we don't use "fmov @Rm, DRn" here.
            $asm.puts "fmov.s #{operands[0].sh4OperandPostInc}, #{operands[1].sh4SingleLo}"
            $asm.puts "fmov.s #{operands[0].sh4OperandPostInc}, #{operands[1].sh4SingleHi}"
        when "storedReversedAndDecrementAddress"
            # As we are little endian, we don't use "fmov DRm, @Rn" here.
            $asm.puts "fmov.s #{operands[0].sh4SingleHi}, #{operands[1].sh4OperandPreDec}"
            $asm.puts "fmov.s #{operands[0].sh4SingleLo}, #{operands[1].sh4OperandPreDec}"
        when "ci2d"
            $asm.puts "lds #{operands[0].sh4Operand}, fpul"
            $asm.puts "float fpul, #{operands[1].sh4Operand}"
        when "fii2d"
            $asm.puts "lds #{operands[0].sh4Operand}, fpul"
            $asm.puts "fsts fpul, #{operands[2].sh4SingleLo}"
            $asm.puts "lds #{operands[1].sh4Operand}, fpul"
            $asm.puts "fsts fpul, #{operands[2].sh4SingleHi}"
        when "fd2ii"
            $asm.puts "flds #{operands[0].sh4SingleLo}, fpul"
            $asm.puts "sts fpul, #{operands[1].sh4Operand}"
            $asm.puts "flds #{operands[0].sh4SingleHi}, fpul"
            $asm.puts "sts fpul, #{operands[2].sh4Operand}"
        when "addd", "subd", "muld", "divd"
            sh4opcode = opcode[0..-2]
            $asm.puts "f#{sh4opcode} #{sh4Operands(operands)}"
        when "bcd2i"
            $asm.puts "ftrc #{operands[0].sh4Operand}, fpul"
            $asm.puts "sts fpul, #{operands[1].sh4Operand}"
            $asm.puts "float fpul, #{SH4_TMP_FPRS[0].sh4Operand}"
            $asm.puts "fcmp/eq #{sh4Operands([operands[0], SH4_TMP_FPRS[0]])}"
            $asm.puts "bf #{operands[2].asmLabel}"
            $asm.puts "tst #{sh4Operands([operands[1], operands[1]])}"
            $asm.puts "bt #{operands[2].asmLabel}"
        when "bdnan"
            emitSH4BranchIfNaN(operands)
        when "bdneq"
            emitSH4DoubleCondBranch("eq", true, operands)
        when "bdgteq"
            emitSH4DoubleCondBranch("lt", true, operands)
        when "bdlt"
            emitSH4DoubleCondBranch("lt", false, operands)
        when "bdlteq"
            emitSH4DoubleCondBranch("gt", true, operands)
        when "bdgt"
            emitSH4DoubleCondBranch("gt", false, operands)
        when "baddio", "baddpo", "bsubio", "bsubpo"
            raise "#{opcode} with #{operands.size} operands is not handled yet" unless operands.size == 3
            $asm.puts "#{opcode[1, 3]}v #{sh4Operands([operands[0], operands[1]])}"
            $asm.puts "bt #{operands[2].asmLabel}"
        when "bmulio", "bmulpo"
            raise "Invalid operands number (#{operands.size})" unless operands.size == 5
            $asm.puts "dmuls.l #{sh4Operands([operands[2], operands[3]])}"
            $asm.puts "sts macl, #{operands[3].sh4Operand}"
            $asm.puts "sts mach, #{operands[0].sh4Operand}"
            $asm.puts "cmp/pz #{operands[3].sh4Operand}"
            $asm.puts "movt #{operands[1].sh4Operand}"
            $asm.puts "dt #{operands[1].sh4Operand}"
            $asm.puts "cmp/eq #{sh4Operands([operands[0], operands[1]])}"
            $asm.puts "bf #{operands[4].asmLabel}"
        when "btiz", "btpz", "btbz", "btinz", "btpnz", "btbnz"
            if operands.size == 3
                $asm.puts "tst #{sh4Operands([operands[0], operands[1]])}"
            else
                if operands[0].sh4Operand == "r0"
                    $asm.puts "cmp/eq #0, r0"
                else
                    $asm.puts "tst #{sh4Operands([operands[0], operands[0]])}"
                end
            end
            emitSH4BranchIfT(operands[-1], (opcode[-2, 2] == "nz"))
        when "cieq", "cpeq", "cbeq"
            emitSH4CompareSet("eq", false, operands)
        when "cineq", "cpneq", "cbneq"
            emitSH4CompareSet("eq", true, operands)
        when "cib", "cpb", "cbb"
            emitSH4CompareSet("hs", true, operands)
        when "bieq", "bpeq", "bbeq"
            emitSH4CondBranch("eq", false, operands)
        when "bineq", "bpneq", "bbneq"
            emitSH4CondBranch("eq", true, operands)
        when "bib", "bpb", "bbb"
            emitSH4CondBranch("hs", true, operands)
        when "bia", "bpa", "bba"
            emitSH4CondBranch("hi", false, operands)
        when "biaeq", "bpaeq"
            emitSH4CondBranch("hs", false, operands)
        when "bigteq", "bpgteq", "bbgteq"
            emitSH4CondBranch("ge", false, operands)
        when "bilt", "bplt", "bblt"
            emitSH4CondBranch("ge", true, operands)
        when "bigt", "bpgt", "bbgt"
            emitSH4CondBranch("gt", false, operands)
        when "bilteq", "bplteq", "bblteq"
            emitSH4CondBranch("gt", true, operands)
        when "bs"
            $asm.puts "cmp/pz #{operands[0].sh4Operand}"
            $asm.puts "bf #{operands[1].asmLabel}"
        when "call"
            if operands[0].is_a? LocalLabelReference
                $asm.puts "bsr #{operands[0].asmLabel}"
                $asm.puts "nop"
            elsif operands[0].is_a? RegisterID or operands[0].is_a? SpecialRegister
                emitSH4Branch("jsr", operands[0])
            else
                raise "Unhandled parameters for opcode #{opcode} at #{codeOriginString}"
            end
        when "callf"
            $asm.puts ".balign 4"
            $asm.puts "mov r0, #{operands[0].sh4Operand}"
            $asm.puts "mova @(14, PC), r0"
            $asm.puts "lds r0, pr"
            $asm.puts "mov.l @(6, PC), #{operands[1].sh4Operand}"
            $asm.puts "jmp @#{operands[1].sh4Operand}"
            $asm.puts "mov #{operands[0].sh4Operand}, r0"
            $asm.puts ".long #{operands[2].asmLabel}"
        when "jmp"
            if operands[0].is_a? LocalLabelReference
                $asm.puts "bra #{operands[0].asmLabel}"
                $asm.puts "nop"
            elsif operands[0].is_a? RegisterID or operands[0].is_a? SpecialRegister
                emitSH4Branch("jmp", operands[0])
            else
                raise "Unhandled parameters for opcode #{opcode} at #{codeOriginString}"
            end
        when "jmpf"
            emitSH4Load32AndJump(operands[1].asmLabel, operands[0])
        when "ret"
            $asm.puts "rts"
            $asm.puts "nop"
        when "loadb"
            $asm.puts "mov.b #{sh4Operands(operands)}"
            $asm.puts "extu.b #{sh4Operands([operands[1], operands[1]])}"
        when "loadh"
            $asm.puts "mov.w #{sh4Operands(operands)}"
            $asm.puts "extu.w #{sh4Operands([operands[1], operands[1]])}"
        when "loadi", "loadis", "loadp", "storei", "storep"
            $asm.puts "mov.l #{sh4Operands(operands)}"
        when "move"
            if operands[0].is_a? LabelReference
                emitSH4Load32(operands[0].asmLabel, operands[1])
            elsif operands[0].is_a? Immediate
                emitSH4LoadImm(operands)
            else
                $asm.puts "mov #{sh4Operands(operands)}"
            end
        when "leap"
            if operands[0].is_a? BaseIndex
                biop = operands[0]
                if biop.scale > 0
                    $asm.puts "mov #{sh4Operands([biop.index, operands[1]])}"
                    if biop.scaleShift > 0
                        emitSH4ShiftImm(biop.scaleShift, operands[1], "l")
                    end
                    $asm.puts "add #{sh4Operands([biop.base, operands[1]])}"
                else
                    $asm.puts "mov #{sh4Operands([biop.base, operands[1]])}"
                end
                if biop.offset.value != 0
                    $asm.puts "add #{sh4Operands([biop.offset, operands[1]])}"
                end
            elsif operands[0].is_a? Address
                if operands[0].base != operands[1]
                    $asm.puts "mov #{sh4Operands([operands[0].base, operands[1]])}"
                end
                if operands[0].offset.value != 0
                    $asm.puts "add #{sh4Operands([operands[0].offset, operands[1]])}"
                end
            else
                raise "Unhandled parameters for opcode #{opcode} at #{codeOriginString}"
            end
        when "ldspr"
            $asm.puts "lds #{sh4Operands(operands)}, pr"
        when "stspr"
            $asm.puts "sts pr, #{sh4Operands(operands)}"
        when "break"
            # This special opcode always generates an illegal instruction exception.
            $asm.puts ".word 0xfffd"
        else
            raise "Unhandled opcode #{opcode} at #{codeOriginString}"
        end
    end
end