# Copyright (C) 2012 Apple Inc. All rights reserved.
# Copyright (C) 2015 The Qt Company Ltd
#
# 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE 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 "config"

def isX64
    case $activeBackend
    when "X86"
        false
    when "X86_64"
        true
    else
        raise "bad value for $activeBackend: #{$activeBackend}"
    end
end

def useX87
    case $activeBackend
    when "X86"
        true
    when "X86_64"
        false
    else
        raise "bad value for $activeBackend: #{$activeBackend}"
    end
end

class SpecialRegister < NoChildren
    def x86Operand(kind)
        raise unless @name =~ /^r/
        raise unless isX64
        case kind
        when :half
            "%" + @name + "w"
        when :int
            "%" + @name + "d"
        when :ptr
            "%" + @name
        when :quad
            "%" + @name
        else
            raise
        end
    end
    def x86CallOperand(kind)
        # Call operands are not allowed to be partial registers.
        "*#{x86Operand(:quad)}"
    end
end

X64_SCRATCH_REGISTER = SpecialRegister.new("r11")

class RegisterID
    def supports8BitOnX86
        case name
        when "t0", "a0", "r0", "t1", "a1", "r1", "t2", "t3"
            true
        when "cfr", "ttnr", "tmr"
            false
        when "t4", "t5"
            isX64
        else
            raise
        end
    end
    
    def x86Operand(kind)
        case name
        when "t0", "a0", "r0"
            case kind
            when :byte
                "%al"
            when :half
                "%ax"
            when :int
                "%eax"
            when :ptr
                isX64 ? "%rax" : "%eax"
            when :quad
                isX64 ? "%rax" : raise
            else
                raise
            end
        when "t1", "a1", "r1"
            case kind
            when :byte
                "%dl"
            when :half
                "%dx"
            when :int
                "%edx"
            when :ptr
                isX64 ? "%rdx" : "%edx"
            when :quad
                isX64 ? "%rdx" : raise
            else
                raise
            end
        when "t2"
            case kind
            when :byte
                "%cl"
            when :half
                "%cx"
            when :int
                "%ecx"
            when :ptr
                isX64 ? "%rcx" : "%ecx"
            when :quad
                isX64 ? "%rcx" : raise
            else
                raise
            end
        when "t3"
            case kind
            when :byte
                "%bl"
            when :half
                "%bx"
            when :int
                "%ebx"
            when :ptr
                isX64 ? "%rbx" : "%ebx"
            when :quad
                isX64 ? "%rbx" : raise
            else
                raise
            end
        when "t4"
            case kind
            when :byte
                "%sil"
            when :half
                "%si"
            when :int
                "%esi"
            when :ptr
                isX64 ? "%rsi" : "%esi"
            when :quad
                isX64 ? "%rsi" : raise
            else
                raise
            end
        when "cfr"
            if isX64
                case kind
                when :half
                    "%r13w"
                when :int
                    "%r13d"
                when :ptr
                    "%r13"
                when :quad
                    "%r13"
                else
                    raise
                end
            else
                case kind
                when :byte
                    "%dil"
                when :half
                    "%di"
                when :int
                    "%edi"
                when :ptr
                    "%edi"
                else
                    raise
                end
            end
        when "sp"
            case kind
            when :byte
                "%spl"
            when :half
                "%sp"
            when :int
                "%esp"
            when :ptr
                isX64 ? "%rsp" : "%esp"
            when :quad
                isX64 ? "%rsp" : raise
            else
                raise
            end
        when "t5"
            raise "Cannot use #{name} in 32-bit X86 at #{codeOriginString}" unless isX64
            case kind
            when :byte
                "%dil"
            when :half
                "%di"
            when :int
                "%edi"
            when :ptr
                "%rdi"
            when :quad
                "%rdi"
            end
        when "t6"
            raise "Cannot use #{name} in 32-bit X86 at #{codeOriginString}" unless isX64
            case kind
            when :half
                "%r10w"
            when :int
                "%r10d"
            when :ptr
                "%r10"
            when :quad
                "%r10"
            end
        when "csr1"
            raise "Cannot use #{name} in 32-bit X86 at #{codeOriginString}" unless isX64
            case kind
            when :half
                "%r14w"
            when :int
                "%r14d"
            when :ptr
                "%r14"
            when :quad
                "%r14"
            end
        when "csr2"
            raise "Cannot use #{name} in 32-bit X86 at #{codeOriginString}" unless isX64
            case kind
            when :half
                "%r15w"
            when :int
                "%r15d"
            when :ptr
                "%r15"
            when :quad
                "%r15"
            end
        else
            raise "Bad register #{name} for X86 at #{codeOriginString}"
        end
    end
    def x86CallOperand(kind)
        isX64 ? "*#{x86Operand(:quad)}" : "*#{x86Operand(:ptr)}"
    end
end

class FPRegisterID
    def x86Operand(kind)
        raise unless kind == :double
        raise if useX87
        case name
        when "ft0", "fa0", "fr"
            "%xmm0"
        when "ft1", "fa1"
            "%xmm1"
        when "ft2", "fa2"
            "%xmm2"
        when "ft3", "fa3"
            "%xmm3"
        when "ft4"
            "%xmm4"
        when "ft5"
            "%xmm5"
        else
            raise "Bad register #{name} for X86 at #{codeOriginString}"
        end
    end
    def x87DefaultStackPosition
        case name
        when "ft0", "fr"
            0
        when "ft1"
            1
        when "ft2", "ft3", "ft4", "ft5"
            raise "Unimplemented register #{name} for X86 at #{codeOriginString}"
        else
            raise "Bad register #{name} for X86 at #{codeOriginString}"
        end
    end
    def x87Operand(offset)
        raise unless useX87
        raise unless offset == 0 or offset == 1
        "%st(#{x87DefaultStackPosition + offset})"
    end
    def x86CallOperand(kind)
        "*#{x86Operand(kind)}"
    end
end

class Immediate
    def validX86Immediate?
        if isX64
            value >= -0x80000000 and value <= 0x7fffffff
        else
            true
        end
    end
    def x86Operand(kind)
        "$#{value}"
    end
    def x86CallOperand(kind)
        "#{value}"
    end
end

class Address
    def supports8BitOnX86
        true
    end
    
    def x86AddressOperand(addressKind)
        "#{offset.value}(#{base.x86Operand(addressKind)})"
    end
    def x86Operand(kind)
        x86AddressOperand(:ptr)
    end
    def x86CallOperand(kind)
        "*#{x86Operand(kind)}"
    end
end

class BaseIndex
    def supports8BitOnX86
        true
    end
    
    def x86AddressOperand(addressKind)
        "#{offset.value}(#{base.x86Operand(addressKind)}, #{index.x86Operand(addressKind)}, #{scale})"
    end
    
    def x86Operand(kind)
        x86AddressOperand(:ptr)
    end

    def x86CallOperand(kind)
        "*#{x86Operand(kind)}"
    end
end

class AbsoluteAddress
    def supports8BitOnX86
        true
    end
    
    def x86AddressOperand(addressKind)
        "#{address.value}"
    end
    
    def x86Operand(kind)
        "#{address.value}"
    end

    def x86CallOperand(kind)
        "*#{address.value}"
    end
end

class LabelReference
    def x86CallOperand(kind)
        asmLabel
    end
end

class LocalLabelReference
    def x86CallOperand(kind)
        asmLabel
    end
end

class Sequence
    def getModifiedListX86_64
        newList = []
        
        @list.each {
            | node |
            newNode = node
            if node.is_a? Instruction
                unless node.opcode == "move"
                    usedScratch = false
                    newOperands = node.operands.map {
                        | operand |
                        if operand.immediate? and not operand.validX86Immediate?
                            if usedScratch
                                raise "Attempt to use scratch register twice at #{operand.codeOriginString}"
                            end
                            newList << Instruction.new(operand.codeOrigin, "move", [operand, X64_SCRATCH_REGISTER])
                            usedScratch = true
                            X64_SCRATCH_REGISTER
                        else
                            operand
                        end
                    }
                    newNode = Instruction.new(node.codeOrigin, node.opcode, newOperands, node.annotation)
                end
            else
                unless node.is_a? Label or
                        node.is_a? LocalLabel or
                        node.is_a? Skip
                    raise "Unexpected #{node.inspect} at #{node.codeOrigin}" 
                end
            end
            if newNode
                newList << newNode
            end
        }
        
        return newList
    end
end

class Instruction
    def x86Operands(*kinds)
        raise unless kinds.size == operands.size
        result = []
        kinds.size.times {
            | idx |
            result << operands[idx].x86Operand(kinds[idx])
        }
        result.join(", ")
    end

    def x86Suffix(kind)
        case kind
        when :byte
            "b"
        when :half
            "w"
        when :int
            "l"
        when :ptr
            isX64 ? "q" : "l"
        when :quad
            isX64 ? "q" : raise
        when :double
            not useX87 ? "sd" : raise
        else
            raise
        end
    end
    
    def x86Bytes(kind)
        case kind
        when :byte
            1
        when :half
            2
        when :int
            4
        when :ptr
            isX64 ? 8 : 4
        when :quad
            isX64 ? 8 : raise
        when :double
            8
        else
            raise
        end
    end
    
    def handleX86OpWithNumOperands(opcode, kind, numOperands)
        if numOperands == 3
            if operands[0] == operands[2]
                $asm.puts "#{opcode} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
            elsif operands[1] == operands[2]
                $asm.puts "#{opcode} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
            else
                $asm.puts "mov#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
                $asm.puts "#{opcode} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
            end
        else
            $asm.puts "#{opcode} #{operands[0].x86Operand(kind)}, #{operands[1].x86Operand(kind)}"
        end
    end
    
    def handleX86Op(opcode, kind)
        handleX86OpWithNumOperands(opcode, kind, operands.size)
    end
    
    def handleX86Shift(opcode, kind)
        if operands[0].is_a? Immediate or operands[0] == RegisterID.forName(nil, "t2")
            $asm.puts "#{opcode} #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(kind)}"
        else
            cx = RegisterID.forName(nil, "t2")
            $asm.puts "xchg#{x86Suffix(:ptr)} #{operands[0].x86Operand(:ptr)}, #{cx.x86Operand(:ptr)}"
            $asm.puts "#{opcode} %cl, #{operands[1].x86Operand(kind)}"
            $asm.puts "xchg#{x86Suffix(:ptr)} #{operands[0].x86Operand(:ptr)}, #{cx.x86Operand(:ptr)}"
        end
    end
    
    def handleX86DoubleBranch(branchOpcode, mode)
        if useX87
            handleX87Compare(mode)
        else
            case mode
            when :normal
                $asm.puts "ucomisd #{operands[1].x86Operand(:double)}, #{operands[0].x86Operand(:double)}"
            when :reverse
                $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}"
            else
                raise mode.inspect
            end
        end
        $asm.puts "#{branchOpcode} #{operands[2].asmLabel}"
    end
    
    def handleX86IntCompare(opcodeSuffix, kind)
        if operands[0].is_a? Immediate and operands[0].value == 0 and operands[1].is_a? RegisterID and (opcodeSuffix == "e" or opcodeSuffix == "ne")
            $asm.puts "test#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}"
        elsif operands[1].is_a? Immediate and operands[1].value == 0 and operands[0].is_a? RegisterID and (opcodeSuffix == "e" or opcodeSuffix == "ne")
            $asm.puts "test#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}"
        else
            $asm.puts "cmp#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}, #{operands[0].x86Operand(kind)}"
        end
    end
    
    def handleX86IntBranch(branchOpcode, kind)
        handleX86IntCompare(branchOpcode[1..-1], kind)
        $asm.puts "#{branchOpcode} #{operands[2].asmLabel}"
    end
    
    def handleX86Set(setOpcode, operand)
        if operand.supports8BitOnX86
            $asm.puts "#{setOpcode} #{operand.x86Operand(:byte)}"
            $asm.puts "movzbl #{operand.x86Operand(:byte)}, #{operand.x86Operand(:int)}"
        else
            ax = RegisterID.new(nil, "t0")
            $asm.puts "xchg#{x86Suffix(:ptr)} #{operand.x86Operand(:ptr)}, #{ax.x86Operand(:ptr)}"
            $asm.puts "#{setOpcode} %al"
            $asm.puts "movzbl %al, %eax"
            $asm.puts "xchg#{x86Suffix(:ptr)} #{operand.x86Operand(:ptr)}, #{ax.x86Operand(:ptr)}"
        end
    end
    
    def handleX86IntCompareSet(setOpcode, kind)
        handleX86IntCompare(setOpcode[3..-1], kind)
        handleX86Set(setOpcode, operands[2])
    end
    
    def handleX86Test(kind)
        value = operands[0]
        case operands.size
        when 2
            mask = Immediate.new(codeOrigin, -1)
        when 3
            mask = operands[1]
        else
            raise "Expected 2 or 3 operands, but got #{operands.size} at #{codeOriginString}"
        end
        
        if mask.is_a? Immediate and mask.value == -1
            if value.is_a? RegisterID
                $asm.puts "test#{x86Suffix(kind)} #{value.x86Operand(kind)}, #{value.x86Operand(kind)}"
            else
                $asm.puts "cmp#{x86Suffix(kind)} $0, #{value.x86Operand(kind)}"
            end
        else
            $asm.puts "test#{x86Suffix(kind)} #{mask.x86Operand(kind)}, #{value.x86Operand(kind)}"
        end
    end
    
    def handleX86BranchTest(branchOpcode, kind)
        handleX86Test(kind)
        $asm.puts "#{branchOpcode} #{operands.last.asmLabel}"
    end
    
    def handleX86SetTest(setOpcode, kind)
        handleX86Test(kind)
        handleX86Set(setOpcode, operands.last)
    end
    
    def handleX86OpBranch(opcode, branchOpcode, kind)
        handleX86OpWithNumOperands(opcode, kind, operands.size - 1)
        case operands.size
        when 4
            jumpTarget = operands[3]
        when 3
            jumpTarget = operands[2]
        else
            raise self.inspect
        end
        $asm.puts "#{branchOpcode} #{jumpTarget.asmLabel}"
    end
    
    def handleX86SubBranch(branchOpcode, kind)
        if operands.size == 4 and operands[1] == operands[2]
            $asm.puts "neg#{x86Suffix(kind)} #{operands[2].x86Operand(kind)}"
            $asm.puts "add#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
        else
            handleX86OpWithNumOperands("sub#{x86Suffix(kind)}", kind, operands.size - 1)
        end
        case operands.size
        when 4
            jumpTarget = operands[3]
        when 3
            jumpTarget = operands[2]
        else
            raise self.inspect
        end
        $asm.puts "#{branchOpcode} #{jumpTarget.asmLabel}"
    end

    def handleX86Add(kind)
        if operands.size == 3 and operands[1] == operands[2]
            unless Immediate.new(nil, 0) == operands[0]
                $asm.puts "add#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
            end
        elsif operands.size == 3 and operands[0].is_a? Immediate
            raise unless operands[1].is_a? RegisterID
            raise unless operands[2].is_a? RegisterID
            if operands[0].value == 0
                unless operands[1] == operands[2]
                    $asm.puts "mov#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
                end
            else
                $asm.puts "lea#{x86Suffix(kind)} #{operands[0].value}(#{operands[1].x86Operand(kind)}), #{operands[2].x86Operand(kind)}"
            end
        elsif operands.size == 3 and operands[0].is_a? RegisterID
            raise unless operands[1].is_a? RegisterID
            raise unless operands[2].is_a? RegisterID
            if operands[0] == operands[2]
                $asm.puts "add#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
            else
                $asm.puts "lea#{x86Suffix(kind)} (#{operands[0].x86Operand(kind)}, #{operands[1].x86Operand(kind)}), #{operands[2].x86Operand(kind)}"
            end
        else
            unless Immediate.new(nil, 0) == operands[0]
                $asm.puts "add#{x86Suffix(kind)} #{x86Operands(kind, kind)}"
            end
        end
    end
    
    def handleX86Sub(kind)
        if operands.size == 3 and operands[1] == operands[2]
            $asm.puts "neg#{x86Suffix(kind)} #{operands[2].x86Operand(kind)}"
            $asm.puts "add#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}"
        else
            handleX86Op("sub#{x86Suffix(kind)}", kind)
        end
    end
    
    def handleX86Mul(kind)
        if operands.size == 3 and operands[0].is_a? Immediate
            $asm.puts "imul#{x86Suffix(kind)} #{x86Operands(kind, kind, kind)}"
        else
            # FIXME: could do some peephole in case the left operand is immediate and it's
            # a power of two.
            handleX86Op("imul#{x86Suffix(kind)}", kind)
        end
    end
    
    def handleMove
        if Immediate.new(nil, 0) == operands[0] and operands[1].is_a? RegisterID
            if isX64
                $asm.puts "xor#{x86Suffix(:quad)} #{operands[1].x86Operand(:quad)}, #{operands[1].x86Operand(:quad)}"
            else
                $asm.puts "xor#{x86Suffix(:ptr)} #{operands[1].x86Operand(:ptr)}, #{operands[1].x86Operand(:ptr)}"
            end
        elsif operands[0] != operands[1]
            if isX64
                $asm.puts "mov#{x86Suffix(:quad)} #{x86Operands(:quad, :quad)}"
            else
                $asm.puts "mov#{x86Suffix(:ptr)} #{x86Operands(:ptr, :ptr)}"
            end
        end
    end
    
    def handleX87Compare(mode)
        case mode
        when :normal
            if (operands[0].x87DefaultStackPosition == 0)
                $asm.puts "fucomi #{operands[1].x87Operand(0)}"
            else
                $asm.puts "fld #{operands[0].x87Operand(0)}"
                $asm.puts "fucomip #{operands[1].x87Operand(1)}"
            end
        when :reverse
            if (operands[1].x87DefaultStackPosition == 0)
                $asm.puts "fucomi #{operands[0].x87Operand(0)}"
            else
                $asm.puts "fld #{operands[1].x87Operand(0)}"
                $asm.puts "fucomip #{operands[0].x87Operand(1)}"
            end
        else
            raise mode.inspect
        end
    end

    def handleX87BinOp(opcode, opcodereverse)
        if (operands[1].x87DefaultStackPosition == 0)
            $asm.puts "#{opcode} #{operands[0].x87Operand(0)}, %st"
        elsif (operands[0].x87DefaultStackPosition == 0)
            $asm.puts "#{opcodereverse} %st, #{operands[1].x87Operand(0)}"
        else
            $asm.puts "fld #{operands[0].x87Operand(0)}"
            $asm.puts "#{opcodereverse}p %st, #{operands[1].x87Operand(1)}"
        end
    end

    def lowerX86
        raise unless $activeBackend == "X86"
        lowerX86Common
    end
    
    def lowerX86_64
        raise unless $activeBackend == "X86_64"
        lowerX86Common
    end
    
    def lowerX86Common
        $asm.codeOrigin codeOriginString if $enableCodeOriginComments
        $asm.annotation annotation if $enableInstrAnnotations

        case opcode
        when "addi"
            handleX86Add(:int)
        when "addp"
            handleX86Add(:ptr)
        when "addq"
            handleX86Add(:quad)
        when "andi"
            handleX86Op("andl", :int)
        when "andp"
            handleX86Op("and#{x86Suffix(:ptr)}", :ptr)
        when "andq"
            handleX86Op("and#{x86Suffix(:quad)}", :quad)
        when "lshifti"
            handleX86Shift("sall", :int)
        when "lshiftp"
            handleX86Shift("sal#{x86Suffix(:ptr)}", :ptr)
        when "lshiftq"
            handleX86Shift("sal#{x86Suffix(:quad)}", :quad)
        when "muli"
            handleX86Mul(:int)
        when "mulp"
            handleX86Mul(:ptr)
        when "mulq"
            handleX86Mul(:quad)
        when "negi"
            $asm.puts "negl #{x86Operands(:int)}"
        when "negp"
            $asm.puts "neg#{x86Suffix(:ptr)} #{x86Operands(:ptr)}"
        when "negq"
            $asm.puts "neg#{x86Suffix(:quad)} #{x86Operands(:quad)}"
        when "noti"
            $asm.puts "notl #{x86Operands(:int)}"
        when "ori"
            handleX86Op("orl", :int)
        when "orp"
            handleX86Op("or#{x86Suffix(:ptr)}", :ptr)
        when "orq"
            handleX86Op("or#{x86Suffix(:quad)}", :quad)
        when "rshifti"
            handleX86Shift("sarl", :int)
        when "rshiftp"
            handleX86Shift("sar#{x86Suffix(:ptr)}", :ptr)
        when "rshiftq"
            handleX86Shift("sar#{x86Suffix(:quad)}", :quad)
        when "urshifti"
            handleX86Shift("shrl", :int)
        when "urshiftp"
            handleX86Shift("shr#{x86Suffix(:ptr)}", :ptr)
        when "urshiftq"
            handleX86Shift("shr#{x86Suffix(:quad)}", :quad)
        when "subi"
            handleX86Sub(:int)
        when "subp"
            handleX86Sub(:ptr)
        when "subq"
            handleX86Sub(:quad)
        when "xori"
            handleX86Op("xorl", :int)
        when "xorp"
            handleX86Op("xor#{x86Suffix(:ptr)}", :ptr)
        when "xorq"
            handleX86Op("xor#{x86Suffix(:quad)}", :quad)
        when "loadi", "storei"
            $asm.puts "movl #{x86Operands(:int, :int)}"
        when "loadis"
            if isX64
                $asm.puts "movslq #{x86Operands(:int, :quad)}"
            else
                $asm.puts "movl #{x86Operands(:int, :int)}"
            end
        when "loadp", "storep"
            $asm.puts "mov#{x86Suffix(:ptr)} #{x86Operands(:ptr, :ptr)}"
        when "loadq", "storeq"
            $asm.puts "mov#{x86Suffix(:quad)} #{x86Operands(:quad, :quad)}"
        when "loadb"
            $asm.puts "movzbl #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(:int)}"
        when "loadbs"
            $asm.puts "movsbl #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(:int)}"
        when "loadh"
            $asm.puts "movzwl #{operands[0].x86Operand(:half)}, #{operands[1].x86Operand(:int)}"
        when "loadhs"
            $asm.puts "movswl #{operands[0].x86Operand(:half)}, #{operands[1].x86Operand(:int)}"
        when "storeb"
            $asm.puts "movb #{x86Operands(:byte, :byte)}"
        when "loadd"
            if useX87
                $asm.puts "fldl #{operands[0].x86Operand(:double)}"
                $asm.puts "fstp #{operands[1].x87Operand(1)}"
            else
                $asm.puts "movsd #{x86Operands(:double, :double)}"
            end
        when "moved"
            if useX87
                if (operands[0].x87DefaultStackPosition == 0)
                    $asm.puts "fst #{operands[1].x87Operand(0)}"
                else
                    $asm.puts "fld #{operands[0].x87Operand(0)}"
                    $asm.puts "fstp #{operands[1].x87Operand(1)}"
                end
            else
                $asm.puts "movsd #{x86Operands(:double, :double)}"
            end
        when "stored"
            if useX87
                if (operands[0].x87DefaultStackPosition == 0)
                    $asm.puts "fstl #{operands[1].x86Operand(:double)}"
                else
                    $asm.puts "fld #{operands[0].x87Operand(0)}"
                    $asm.puts "fstpl #{operands[1].x86Operand(:double)}"
                end
            else
                $asm.puts "movsd #{x86Operands(:double, :double)}"
            end
        when "addd"
            if useX87
                handleX87BinOp("fadd", "fadd")
            else
                $asm.puts "addsd #{x86Operands(:double, :double)}"
            end
        when "muld"
            if useX87
                handleX87BinOp("fmul", "fmul")
            else
                $asm.puts "mulsd #{x86Operands(:double, :double)}"
            end
        when "subd"
            if useX87
                handleX87BinOp("fsub", "fsubr")
            else
                $asm.puts "subsd #{x86Operands(:double, :double)}"
            end
        when "divd"
            if useX87
                handleX87BinOp("fdiv", "fdivr")
            else
                $asm.puts "divsd #{x86Operands(:double, :double)}"
            end
        when "sqrtd"
            if useX87
                $asm.puts "fld #{operands[0].x87Operand(0)}"
                $asm.puts "fsqrtl"
                $asm.puts "fstp #{operands[1].x87Operand(1)}"
            else
                $asm.puts "sqrtsd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}"
            end
        when "ci2d"
            if useX87
                sp = RegisterID.new(nil, "sp")
                $asm.puts "movl #{operands[0].x86Operand(:int)}, -4(#{sp.x86Operand(:ptr)})"
                $asm.puts "fildl -4(#{sp.x86Operand(:ptr)})"
                $asm.puts "fstp #{operands[1].x87Operand(1)}"
            else
                $asm.puts "cvtsi2sd #{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:double)}"
            end
        when "bdeq"
            if useX87
                handleX87Compare(:normal)
            else
                $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}"
            end
            if operands[0] == operands[1]
                # This is just a jump ordered, which is a jnp.
                $asm.puts "jnp #{operands[2].asmLabel}"
            else
                isUnordered = LocalLabel.unique("bdeq")
                $asm.puts "jp #{LabelReference.new(codeOrigin, isUnordered).asmLabel}"
                $asm.puts "je #{LabelReference.new(codeOrigin, operands[2]).asmLabel}"
                isUnordered.lower("X86")
            end
        when "bdneq"
            handleX86DoubleBranch("jne", :normal)
        when "bdgt"
            handleX86DoubleBranch("ja", :normal)
        when "bdgteq"
            handleX86DoubleBranch("jae", :normal)
        when "bdlt"
            handleX86DoubleBranch("ja", :reverse)
        when "bdlteq"
            handleX86DoubleBranch("jae", :reverse)
        when "bdequn"
            handleX86DoubleBranch("je", :normal)
        when "bdnequn"
            if useX87
                handleX87Compare(:normal)
            else
                $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}"
            end
            if operands[0] == operands[1]
                # This is just a jump unordered, which is a jp.
                $asm.puts "jp #{operands[2].asmLabel}"
            else
                isUnordered = LocalLabel.unique("bdnequn")
                isEqual = LocalLabel.unique("bdnequn")
                $asm.puts "jp #{LabelReference.new(codeOrigin, isUnordered).asmLabel}"
                $asm.puts "je #{LabelReference.new(codeOrigin, isEqual).asmLabel}"
                isUnordered.lower("X86")
                $asm.puts "jmp #{operands[2].asmLabel}"
                isEqual.lower("X86")
            end
        when "bdgtun"
            handleX86DoubleBranch("jb", :reverse)
        when "bdgtequn"
            handleX86DoubleBranch("jbe", :reverse)
        when "bdltun"
            handleX86DoubleBranch("jb", :normal)
        when "bdltequn"
            handleX86DoubleBranch("jbe", :normal)
        when "btd2i"
            # FIXME: unused and unimplemented for x87
            raise if useX87
            $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}"
            $asm.puts "cmpl $0x80000000 #{operands[1].x86Operand(:int)}"
            $asm.puts "je #{operands[2].asmLabel}"
        when "td2i"
            # FIXME: unused and unimplemented for x87
            raise if useX87
            $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}"
        when "bcd2i"
            if useX87
                sp = RegisterID.new(nil, "sp")
                if (operands[0].x87DefaultStackPosition == 0)
                    $asm.puts "fistl -4(#{sp.x86Operand(:ptr)})"
                else
                    $asm.puts "fld #{operands[0].x87Operand(0)}"
                    $asm.puts "fistpl -4(#{sp.x86Operand(:ptr)})"
                end
                $asm.puts "movl -4(#{sp.x86Operand(:ptr)}), #{operands[1].x86Operand(:int)}"
                $asm.puts "testl #{operands[1].x86Operand(:int)}, #{operands[1].x86Operand(:int)}"
                $asm.puts "je #{operands[2].asmLabel}"
                $asm.puts "fildl -4(#{sp.x86Operand(:ptr)})"
                $asm.puts "fucomip #{operands[0].x87Operand(1)}"
                $asm.puts "jp #{operands[2].asmLabel}"
                $asm.puts "jne #{operands[2].asmLabel}"
            else
                $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}"
                $asm.puts "testl #{operands[1].x86Operand(:int)}, #{operands[1].x86Operand(:int)}"
                $asm.puts "je #{operands[2].asmLabel}"
                $asm.puts "cvtsi2sd #{operands[1].x86Operand(:int)}, %xmm7"
                $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, %xmm7"
                $asm.puts "jp #{operands[2].asmLabel}"
                $asm.puts "jne #{operands[2].asmLabel}"
            end
        when "movdz"
            if useX87
                $asm.puts "fldzl"
                $asm.puts "fstp #{operands[0].x87Operand(1)}"
            else
                $asm.puts "xorpd #{operands[0].x86Operand(:double)}, #{operands[0].x86Operand(:double)}"
            end
        when "pop"
            $asm.puts "pop #{operands[0].x86Operand(:ptr)}"
        when "push"
            $asm.puts "push #{operands[0].x86Operand(:ptr)}"
        when "move"
            handleMove
        when "sxi2q"
            $asm.puts "movslq #{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:quad)}"
        when "zxi2q"
            $asm.puts "movl #{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:int)}"
        when "nop"
            $asm.puts "nop"
        when "bieq"
            handleX86IntBranch("je", :int)
        when "bpeq"
            handleX86IntBranch("je", :ptr)
        when "bqeq"
            handleX86IntBranch("je", :quad)
        when "bineq"
            handleX86IntBranch("jne", :int)
        when "bpneq"
            handleX86IntBranch("jne", :ptr)
        when "bqneq"
            handleX86IntBranch("jne", :quad)
        when "bia"
            handleX86IntBranch("ja", :int)
        when "bpa"
            handleX86IntBranch("ja", :ptr)
        when "bqa"
            handleX86IntBranch("ja", :quad)
        when "biaeq"
            handleX86IntBranch("jae", :int)
        when "bpaeq"
            handleX86IntBranch("jae", :ptr)
        when "bqaeq"
            handleX86IntBranch("jae", :quad)
        when "bib"
            handleX86IntBranch("jb", :int)
        when "bpb"
            handleX86IntBranch("jb", :ptr)
        when "bqb"
            handleX86IntBranch("jb", :quad)
        when "bibeq"
            handleX86IntBranch("jbe", :int)
        when "bpbeq"
            handleX86IntBranch("jbe", :ptr)
        when "bqbeq"
            handleX86IntBranch("jbe", :quad)
        when "bigt"
            handleX86IntBranch("jg", :int)
        when "bpgt"
            handleX86IntBranch("jg", :ptr)
        when "bqgt"
            handleX86IntBranch("jg", :quad)
        when "bigteq"
            handleX86IntBranch("jge", :int)
        when "bpgteq"
            handleX86IntBranch("jge", :ptr)
        when "bqgteq"
            handleX86IntBranch("jge", :quad)
        when "bilt"
            handleX86IntBranch("jl", :int)
        when "bplt"
            handleX86IntBranch("jl", :ptr)
        when "bqlt"
            handleX86IntBranch("jl", :quad)
        when "bilteq"
            handleX86IntBranch("jle", :int)
        when "bplteq"
            handleX86IntBranch("jle", :ptr)
        when "bqlteq"
            handleX86IntBranch("jle", :quad)
        when "bbeq"
            handleX86IntBranch("je", :byte)
        when "bbneq"
            handleX86IntBranch("jne", :byte)
        when "bba"
            handleX86IntBranch("ja", :byte)
        when "bbaeq"
            handleX86IntBranch("jae", :byte)
        when "bbb"
            handleX86IntBranch("jb", :byte)
        when "bbbeq"
            handleX86IntBranch("jbe", :byte)
        when "bbgt"
            handleX86IntBranch("jg", :byte)
        when "bbgteq"
            handleX86IntBranch("jge", :byte)
        when "bblt"
            handleX86IntBranch("jl", :byte)
        when "bblteq"
            handleX86IntBranch("jlteq", :byte)
        when "btis"
            handleX86BranchTest("js", :int)
        when "btps"
            handleX86BranchTest("js", :ptr)
        when "btqs"
            handleX86BranchTest("js", :quad)
        when "btiz"
            handleX86BranchTest("jz", :int)
        when "btpz"
            handleX86BranchTest("jz", :ptr)
        when "btqz"
            handleX86BranchTest("jz", :quad)
        when "btinz"
            handleX86BranchTest("jnz", :int)
        when "btpnz"
            handleX86BranchTest("jnz", :ptr)
        when "btqnz"
            handleX86BranchTest("jnz", :quad)
        when "btbs"
            handleX86BranchTest("js", :byte)
        when "btbz"
            handleX86BranchTest("jz", :byte)
        when "btbnz"
            handleX86BranchTest("jnz", :byte)
        when "jmp"
            $asm.puts "jmp #{operands[0].x86CallOperand(:ptr)}"
        when "baddio"
            handleX86OpBranch("addl", "jo", :int)
        when "baddpo"
            handleX86OpBranch("add#{x86Suffix(:ptr)}", "jo", :ptr)
        when "baddqo"
            handleX86OpBranch("add#{x86Suffix(:quad)}", "jo", :quad)
        when "baddis"
            handleX86OpBranch("addl", "js", :int)
        when "baddps"
            handleX86OpBranch("add#{x86Suffix(:ptr)}", "js", :ptr)
        when "baddqs"
            handleX86OpBranch("add#{x86Suffix(:quad)}", "js", :quad)
        when "baddiz"
            handleX86OpBranch("addl", "jz", :int)
        when "baddpz"
            handleX86OpBranch("add#{x86Suffix(:ptr)}", "jz", :ptr)
        when "baddqz"
            handleX86OpBranch("add#{x86Suffix(:quad)}", "jz", :quad)
        when "baddinz"
            handleX86OpBranch("addl", "jnz", :int)
        when "baddpnz"
            handleX86OpBranch("add#{x86Suffix(:ptr)}", "jnz", :ptr)
        when "baddqnz"
            handleX86OpBranch("add#{x86Suffix(:quad)}", "jnz", :quad)
        when "bsubio"
            handleX86SubBranch("jo", :int)
        when "bsubis"
            handleX86SubBranch("js", :int)
        when "bsubiz"
            handleX86SubBranch("jz", :int)
        when "bsubinz"
            handleX86SubBranch("jnz", :int)
        when "bmulio"
            handleX86OpBranch("imull", "jo", :int)
        when "bmulis"
            handleX86OpBranch("imull", "js", :int)
        when "bmuliz"
            handleX86OpBranch("imull", "jz", :int)
        when "bmulinz"
            handleX86OpBranch("imull", "jnz", :int)
        when "borio"
            handleX86OpBranch("orl", "jo", :int)
        when "boris"
            handleX86OpBranch("orl", "js", :int)
        when "boriz"
            handleX86OpBranch("orl", "jz", :int)
        when "borinz"
            handleX86OpBranch("orl", "jnz", :int)
        when "break"
            $asm.puts "int $3"
        when "call"
            if useX87
                2.times {
                    | offset |
                    $asm.puts "ffree %st(#{offset})"
                }
            end
            $asm.puts "call #{operands[0].x86CallOperand(:ptr)}"
        when "ret"
            $asm.puts "ret"
        when "cieq"
            handleX86IntCompareSet("sete", :int)
        when "cbeq"
            handleX86IntCompareSet("sete", :byte)
        when "cpeq"
            handleX86IntCompareSet("sete", :ptr)
        when "cqeq"
            handleX86IntCompareSet("sete", :quad)
        when "cineq"
            handleX86IntCompareSet("setne", :int)
        when "cbneq"
            handleX86IntCompareSet("setne", :byte)
        when "cpneq"
            handleX86IntCompareSet("setne", :ptr)
        when "cqneq"
            handleX86IntCompareSet("setne", :quad)
        when "cia"
            handleX86IntCompareSet("seta", :int)
        when "cba"
            handleX86IntCompareSet("seta", :byte)
        when "cpa"
            handleX86IntCompareSet("seta", :ptr)
        when "cqa"
            handleX86IntCompareSet("seta", :quad)
        when "ciaeq"
            handleX86IntCompareSet("setae", :int)
        when "cbaeq"
            handleX86IntCompareSet("setae", :byte)
        when "cpaeq"
            handleX86IntCompareSet("setae", :ptr)
        when "cqaeq"
            handleX86IntCompareSet("setae", :quad)
        when "cib"
            handleX86IntCompareSet("setb", :int)
        when "cbb"
            handleX86IntCompareSet("setb", :byte)
        when "cpb"
            handleX86IntCompareSet("setb", :ptr)
        when "cqb"
            handleX86IntCompareSet("setb", :quad)
        when "cibeq"
            handleX86IntCompareSet("setbe", :int)
        when "cbbeq"
            handleX86IntCompareSet("setbe", :byte)
        when "cpbeq"
            handleX86IntCompareSet("setbe", :ptr)
        when "cqbeq"
            handleX86IntCompareSet("setbe", :quad)
        when "cigt"
            handleX86IntCompareSet("setg", :int)
        when "cbgt"
            handleX86IntCompareSet("setg", :byte)
        when "cpgt"
            handleX86IntCompareSet("setg", :ptr)
        when "cqgt"
            handleX86IntCompareSet("setg", :quad)
        when "cigteq"
            handleX86IntCompareSet("setge", :int)
        when "cbgteq"
            handleX86IntCompareSet("setge", :byte)
        when "cpgteq"
            handleX86IntCompareSet("setge", :ptr)
        when "cqgteq"
            handleX86IntCompareSet("setge", :quad)
        when "cilt"
            handleX86IntCompareSet("setl", :int)
        when "cblt"
            handleX86IntCompareSet("setl", :byte)
        when "cplt"
            handleX86IntCompareSet("setl", :ptr)
        when "cqlt"
            handleX86IntCompareSet("setl", :quad)
        when "cilteq"
            handleX86IntCompareSet("setle", :int)
        when "cblteq"
            handleX86IntCompareSet("setle", :byte)
        when "cplteq"
            handleX86IntCompareSet("setle", :ptr)
        when "cqlteq"
            handleX86IntCompareSet("setle", :quad)
        when "tis"
            handleX86SetTest("sets", :int)
        when "tiz"
            handleX86SetTest("setz", :int)
        when "tinz"
            handleX86SetTest("setnz", :int)
        when "tps"
            handleX86SetTest("sets", :ptr)
        when "tpz"
            handleX86SetTest("setz", :ptr)
        when "tpnz"
            handleX86SetTest("setnz", :ptr)
        when "tqs"
            handleX86SetTest("sets", :quad)
        when "tqz"
            handleX86SetTest("setz", :quad)
        when "tqnz"
            handleX86SetTest("setnz", :quad)
        when "tbs"
            handleX86SetTest("sets", :byte)
        when "tbz"
            handleX86SetTest("setz", :byte)
        when "tbnz"
            handleX86SetTest("setnz", :byte)
        when "peek"
            sp = RegisterID.new(nil, "sp")
            $asm.puts "mov#{x86Suffix(:ptr)} #{operands[0].value * x86Bytes(:ptr)}(#{sp.x86Operand(:ptr)}), #{operands[1].x86Operand(:ptr)}"
        when "peekq"
            sp = RegisterID.new(nil, "sp")
            $asm.puts "mov#{x86Suffix(:quad)} #{operands[0].value * x86Bytes(:quad)}(#{sp.x86Operand(:ptr)}), #{operands[1].x86Operand(:quad)}"
        when "poke"
            sp = RegisterID.new(nil, "sp")
            $asm.puts "mov#{x86Suffix(:ptr)} #{operands[0].x86Operand(:ptr)}, #{operands[1].value * x86Bytes(:ptr)}(#{sp.x86Operand(:ptr)})"
        when "pokeq"
            sp = RegisterID.new(nil, "sp")
            $asm.puts "mov#{x86Suffix(:quad)} #{operands[0].x86Operand(:quad)}, #{operands[1].value * x86Bytes(:quad)}(#{sp.x86Operand(:ptr)})"
        when "cdqi"
            $asm.puts "cdq"
        when "idivi"
            $asm.puts "idivl #{operands[0].x86Operand(:int)}"
        when "fii2d"
            if useX87
                sp = RegisterID.new(nil, "sp")
                $asm.puts "movl #{operands[0].x86Operand(:int)}, -8(#{sp.x86Operand(:ptr)})"
                $asm.puts "movl #{operands[1].x86Operand(:int)}, -4(#{sp.x86Operand(:ptr)})"
                $asm.puts "fldl -8(#{sp.x86Operand(:ptr)})"
                $asm.puts "fstp #{operands[2].x87Operand(1)}"
            else
                $asm.puts "movd #{operands[0].x86Operand(:int)}, #{operands[2].x86Operand(:double)}"
                $asm.puts "movd #{operands[1].x86Operand(:int)}, %xmm7"
                $asm.puts "psllq $32, %xmm7"
                $asm.puts "por %xmm7, #{operands[2].x86Operand(:double)}"
            end
        when "fd2ii"
            if useX87
                sp = RegisterID.new(nil, "sp")
                if (operands[0].x87DefaultStackPosition == 0)
                    $asm.puts "fstl -8(#{sp.x86Operand(:ptr)})"
                else
                    $asm.puts "fld #{operands[0].x87Operand(0)}"
                    $asm.puts "fstpl -8(#{sp.x86Operand(:ptr)})"
                end
                $asm.puts "movl -8(#{sp.x86Operand(:ptr)}), #{operands[1].x86Operand(:int)}"
                $asm.puts "movl -4(#{sp.x86Operand(:ptr)}), #{operands[2].x86Operand(:int)}"
            else
                $asm.puts "movd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}"
                $asm.puts "movsd #{operands[0].x86Operand(:double)}, %xmm7"
                $asm.puts "psrlq $32, %xmm7"
                $asm.puts "movd %xmm7, #{operands[2].x86Operand(:int)}"
            end
        when "fq2d"
            if useX87
                sp = RegisterID.new(nil, "sp")
                $asm.puts "movq #{operands[0].x86Operand(:quad)}, -8(#{sp.x86Operand(:ptr)})"
                $asm.puts "fldl -8(#{sp.x86Operand(:ptr)})"
                $asm.puts "fstp #{operands[1].x87Operand(1)}"
            else
                $asm.puts "movq #{operands[0].x86Operand(:quad)}, #{operands[1].x86Operand(:double)}"
            end
        when "fd2q"
            if useX87
                sp = RegisterID.new(nil, "sp")
                if (operands[0].x87DefaultStackPosition == 0)
                    $asm.puts "fstl -8(#{sp.x86Operand(:ptr)})"
                else
                    $asm.puts "fld #{operands[0].x87Operand(0)}"
                    $asm.puts "fstpl -8(#{sp.x86Operand(:ptr)})"
                end
                $asm.puts "movq -8(#{sp.x86Operand(:ptr)}), #{operands[1].x86Operand(:quad)}"
            else
                $asm.puts "movq #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:quad)}"
            end
        when "bo"
            $asm.puts "jo #{operands[0].asmLabel}"
        when "bs"
            $asm.puts "js #{operands[0].asmLabel}"
        when "bz"
            $asm.puts "jz #{operands[0].asmLabel}"
        when "bnz"
            $asm.puts "jnz #{operands[0].asmLabel}"
        when "leai"
            $asm.puts "leal #{operands[0].x86AddressOperand(:int)}, #{operands[1].x86Operand(:int)}"
        when "leap"
            $asm.puts "lea#{x86Suffix(:ptr)} #{operands[0].x86AddressOperand(:ptr)}, #{operands[1].x86Operand(:ptr)}"
        else
            lowerDefault
        end
    end
end

