--- /dev/null
+# Copyright (C) 2011, 2012 Apple 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 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 "ast"
+require "opt"
+require "risc"
+require "risc_arm64"
+
+# Naming conventions:
+#
+# x<number> => GPR. This is both the generic name of the register, and the name used
+# to indicate that the register is used in 64-bit mode.
+# w<number> => GPR in 32-bit mode. This is the low 32-bits of the GPR. If it is
+# mutated then the high 32-bit part of the register is zero filled.
+# q<number> => FPR. This is the generic name of the register.
+# d<number> => FPR used as an IEEE 64-bit binary floating point number (i.e. double).
+#
+# GPR conventions, to match the baseline JIT:
+#
+# x0 => return value, cached result, first argument, t0, a0, r0
+# x1 => t1, a1, r1
+# x2 => t2
+# x9 => (nonArgGPR1 in baseline)
+# x10 => t4 (unused in baseline)
+# x11 => t5 (unused in baseline)
+# x12 => t6 (unused in baseline)
+# x13 => scratch (unused in baseline)
+# x16 => scratch
+# x17 => scratch
+# x23 => t3
+# x25 => cfr
+# x26 => timeout check (i.e. not touched by LLInt)
+# x27 => csr1 (tagTypeNumber)
+# x28 => csr2 (tagMask)
+# sp => sp
+# lr => lr
+#
+# FPR conentions, to match the baseline JIT:
+#
+# q0 => ft0
+# q1 => ft1
+# q2 => ft2
+# q3 => ft3
+# q4 => ft4 (unused in baseline)
+# q5 => ft5 (unused in baseline)
+# q31 => scratch
+
+def arm64GPRName(name, kind)
+ raise "bad GPR name #{name}" unless name =~ /^x/
+ number = name[1..-1]
+ case kind
+ when :int
+ "w" + number
+ when :ptr
+ "x" + number
+ else
+ raise "Wrong kind: #{kind}"
+ end
+end
+
+def arm64FPRName(name, kind)
+ raise "bad FPR kind #{kind}" unless kind == :double
+ raise "bad FPR name #{name}" unless name =~ /^q/
+ "d" + name[1..-1]
+end
+
+class SpecialRegister
+ def arm64Operand(kind)
+ case @name
+ when /^x/
+ arm64GPRName(@name, kind)
+ when /^q/
+ arm64FPRName(@name, kind)
+ else
+ raise "Bad name: #{@name}"
+ end
+ end
+end
+
+ARM64_EXTRA_GPRS = [SpecialRegister.new("x16"), SpecialRegister.new("x17"), SpecialRegister.new("x13")]
+ARM64_EXTRA_FPRS = [SpecialRegister.new("q31")]
+
+class RegisterID
+ def arm64Operand(kind)
+ case @name
+ when 't0', 'a0', 'r0'
+ arm64GPRName('x0', kind)
+ when 't1', 'a1', 'r1'
+ arm64GPRName('x1', kind)
+ when 't2'
+ arm64GPRName('x2', kind)
+ when 't3'
+ arm64GPRName('x23', kind)
+ when 't4'
+ arm64GPRName('x10', kind)
+ when 't5'
+ arm64GPRName('x11', kind)
+ when 't6'
+ arm64GPRName('x12', kind)
+ when 'cfr'
+ arm64GPRName('x25', kind)
+ when 'csr1'
+ arm64GPRName('x27', kind)
+ when 'csr2'
+ arm64GPRName('x28', kind)
+ when 'sp'
+ 'sp'
+ when 'lr'
+ 'lr'
+ else
+ raise "Bad register name #{@name} at #{codeOriginString}"
+ end
+ end
+end
+
+class FPRegisterID
+ def arm64Operand(kind)
+ case @name
+ when 'ft0'
+ arm64FPRName('q0', kind)
+ when 'ft1'
+ arm64FPRName('q1', kind)
+ when 'ft2'
+ arm64FPRName('q2', kind)
+ when 'ft3'
+ arm64FPRName('q3', kind)
+ when 'ft4'
+ arm64FPRName('q4', kind)
+ when 'ft5'
+ arm64FPRName('q5', kind)
+ else "Bad register name #{@name} at #{codeOriginString}"
+ end
+ end
+end
+
+class Immediate
+ def arm64Operand(kind)
+ raise "Invalid immediate #{value} at #{codeOriginString}" if value < 0 or value > 4095
+ "\##{value}"
+ end
+end
+
+class Address
+ def arm64Operand(kind)
+ raise "Invalid offset #{offset.value} at #{codeOriginString}" if offset.value < -255 or offset.value > 4095
+ "[#{base.arm64Operand(:ptr)}, \##{offset.value}]"
+ end
+
+ def arm64EmitLea(destination, kind)
+ $asm.puts "add #{destination.arm64Operand(kind)}, #{base.arm64Operand(kind)}, \##{offset.value}"
+ end
+end
+
+class BaseIndex
+ def arm64Operand(kind)
+ raise "Invalid offset #{offset.value} at #{codeOriginString}" if offset.value != 0
+ "[#{base.arm64Operand(:ptr)}, #{index.arm64Operand(:ptr)}, lsl \##{scaleShift}]"
+ end
+
+ def arm64EmitLea(destination, kind)
+ $asm.puts "add #{destination.arm64Operand(kind)}, #{base.arm64Operand(kind)}, #{index.arm64Operand(kind)}, lsl \##{scaleShift}"
+ end
+end
+
+class AbsoluteAddress
+ def arm64Operand(kind)
+ raise "Unconverted absolute address #{address.value} at #{codeOriginString}"
+ end
+end
+
+# FIXME: we could support AbsoluteAddress for lea, but we don't.
+
+#
+# Actual lowering code follows.
+#
+
+class Sequence
+ def getModifiedListARM64
+ result = @list
+ result = riscLowerNot(result)
+ result = riscLowerSimpleBranchOps(result)
+ result = riscLowerHardBranchOps64(result)
+ result = riscLowerShiftOps(result)
+ result = riscLowerMalformedAddresses(result) {
+ | node, address |
+ case node.opcode
+ when "loadb", "loadbs", "storeb", /^bb/, /^btb/, /^cb/, /^tb/
+ size = 1
+ when "loadh", "loadhs"
+ size = 2
+ when "loadi", "loadis", "storei", "addi", "andi", "lshifti", "muli", "negi",
+ "noti", "ori", "rshifti", "urshifti", "subi", "xori", /^bi/, /^bti/,
+ /^ci/, /^ti/, "addis", "subis", "mulis", "smulli", "leai"
+ size = 4
+ when "loadp", "storep", "loadq", "storeq", "loadd", "stored", "lshiftp", "lshiftq", "negp", "negq", "rshiftp", "rshiftq",
+ "urshiftp", "urshiftq", "addp", "addq", "mulp", "mulq", "andp", "andq", "orp", "orq", "subp", "subq", "xorp", "xorq", "addd",
+ "divd", "subd", "muld", "sqrtd", /^bp/, /^bq/, /^btp/, /^btq/, /^cp/, /^cq/, /^tp/, /^tq/, /^bd/,
+ "jmp", "call", "leap", "leaq"
+ size = 8
+ else
+ raise "Bad instruction #{node.opcode} for heap access at #{node.codeOriginString}"
+ end
+
+ if address.is_a? BaseIndex
+ address.offset.value == 0 and
+ (node.opcode =~ /^lea/ or address.scale == 1 or address.scale == size)
+ elsif address.is_a? Address
+ (-255..4095).include? address.offset.value
+ else
+ false
+ end
+ }
+ result = riscLowerMisplacedImmediates(result, ["storeb", "storei", "storep", "storeq"])
+ result = riscLowerMalformedImmediates(result, 0..4095)
+ result = riscLowerMisplacedAddresses(result)
+ result = riscLowerMalformedAddresses(result) {
+ | node, address |
+ case node.opcode
+ when /^load/
+ true
+ when /^store/
+ not (address.is_a? Address and address.offset.value < 0)
+ when /^lea/
+ true
+ else
+ raise "Bad instruction #{node.opcode} for heap access at #{node.codeOriginString}"
+ end
+ }
+ result = riscLowerTest(result)
+ result = assignRegistersToTemporaries(result, :gpr, ARM64_EXTRA_GPRS)
+ result = assignRegistersToTemporaries(result, :fpr, ARM64_EXTRA_FPRS)
+ return result
+ end
+end
+
+def arm64Operands(operands, kinds)
+ if kinds.is_a? Array
+ raise "Mismatched operand lists: #{operands.inspect} and #{kinds.inspect}" if operands.size != kinds.size
+ else
+ kinds = operands.map{ kinds }
+ end
+ (0...operands.size).map {
+ | index |
+ operands[index].arm64Operand(kinds[index])
+ }.join(', ')
+end
+
+def arm64FlippedOperands(operands, kinds)
+ if kinds.is_a? Array
+ kinds = [kinds[-1]] + kinds[0..-2]
+ end
+ arm64Operands([operands[-1]] + operands[0..-2], kinds)
+end
+
+# TAC = three address code.
+def arm64TACOperands(operands, kind)
+ if operands.size == 3
+ return arm64FlippedOperands(operands, kind)
+ end
+
+ raise unless operands.size == 2
+
+ return operands[1].arm64Operand(kind) + ", " + arm64FlippedOperands(operands, kind)
+end
+
+def emitARM64Add(opcode, operands, kind)
+ if operands.size == 3
+ raise unless operands[1].register?
+ raise unless operands[2].register?
+
+ if operands[0].immediate?
+ if operands[0].value == 0 and flag !~ /s$/
+ unless operands[1] == operands[2]
+ $asm.puts "mov #{arm64FlippedOperands(operands[1..2], kind)}"
+ end
+ else
+ $asm.puts "#{opcode} #{arm64Operands(operands.reverse, kind)}"
+ end
+ return
+ end
+
+ raise unless operands[0].register?
+ $asm.puts "#{opcode} #{arm64FlippedOperands(operands, kind)}"
+ return
+ end
+
+ raise unless operands.size == 2
+
+ if operands[0].immediate? and operands[0].value == 0 and opcode !~ /s$/
+ return
+ end
+
+ $asm.puts "#{opcode} #{arm64TACOperands(operands, kind)}"
+end
+
+def emitARM64Unflipped(opcode, operands, kind)
+ $asm.puts "#{opcode} #{arm64Operands(operands, kind)}"
+end
+
+def emitARM64TAC(opcode, operands, kind)
+ $asm.puts "#{opcode} #{arm64TACOperands(operands, kind)}"
+end
+
+def emitARM64(opcode, operands, kind)
+ $asm.puts "#{opcode} #{arm64FlippedOperands(operands, kind)}"
+end
+
+def emitARM64Access(opcode, opcodeNegativeOffset, register, memory, kind)
+ if memory.is_a? Address and memory.offset.value < 0
+ $asm.puts "#{opcodeNegativeOffset} #{register.arm64Operand(kind)}, #{memory.arm64Operand(kind)}"
+ return
+ end
+
+ $asm.puts "#{opcode} #{register.arm64Operand(kind)}, #{memory.arm64Operand(kind)}"
+end
+
+def emitARM64Shift(opcodeRegs, opcodeImmediate, operands, kind)
+ if operands.size == 3 and operands[1].immediate?
+ magicNumbers = yield operands[1].value
+ $asm.puts "#{opcodeImmediate} #{operands[2].arm64Operand(kind)}, #{operands[0].arm64Operand(kind)}, \##{magicNumbers[0]}, \##{magicNumbers[1]}"
+ return
+ end
+
+ if operands.size == 2 and operands[0].immediate?
+ magicNumbers = yield operands[0].value
+ $asm.puts "#{opcodeImmediate} #{operands[1].arm64Operand(kind)}, #{operands[1].arm64Operand(kind)}, \##{magicNumbers[0]}, \##{magicNumbers[1]}"
+ return
+ end
+
+ emitARM64TAC(opcodeRegs, operands, kind)
+end
+
+def emitARM64Branch(opcode, operands, kind, branchOpcode)
+ emitARM64Unflipped(opcode, operands[0..-2], kind)
+ $asm.puts "#{branchOpcode} #{operands[-1].asmLabel}"
+end
+
+def emitARM64Compare(operands, kind, compareCode)
+ emitARM64Unflipped("subs #{arm64GPRName('xzr', kind)}, ", operands[0..-2], kind)
+ $asm.puts "csinc #{operands[-1].arm64Operand(:int)}, wzr, wzr, #{compareCode}"
+end
+
+def emitARM64MoveImmediate(value, target)
+ first = true
+ isNegative = value < 0
+ [48, 32, 16, 0].each {
+ | shift |
+ currentValue = (value >> shift) & 0xffff
+ next if currentValue == (isNegative ? 0xffff : 0) and shift != 0
+ if first
+ if isNegative
+ $asm.puts "movn #{target.arm64Operand(:ptr)}, \##{(~currentValue) & 0xffff}, lsl \##{shift}"
+ else
+ $asm.puts "movz #{target.arm64Operand(:ptr)}, \##{currentValue}, lsl \##{shift}"
+ end
+ first = false
+ else
+ $asm.puts "movk #{target.arm64Operand(:ptr)}, \##{currentValue}, lsl \##{shift}"
+ end
+ }
+end
+
+class Instruction
+ def lowerARM64
+ $asm.comment codeOriginString
+ case opcode
+ when 'addi'
+ emitARM64Add("add", operands, :int)
+ when 'addis'
+ emitARM64Add("adds", operands, :int)
+ when 'addp'
+ emitARM64Add("add", operands, :ptr)
+ when 'addps'
+ emitARM64Add("adds", operands, :ptr)
+ when 'addq'
+ emitARM64Add("add", operands, :ptr)
+ when "andi"
+ emitARM64TAC("and", operands, :int)
+ when "andp"
+ emitARM64TAC("and", operands, :ptr)
+ when "andq"
+ emitARM64TAC("and", operands, :ptr)
+ when "ori"
+ emitARM64TAC("orr", operands, :int)
+ when "orp"
+ emitARM64TAC("orr", operands, :ptr)
+ when "orq"
+ emitARM64TAC("orr", operands, :ptr)
+ when "xori"
+ emitARM64TAC("eor", operands, :int)
+ when "xorp"
+ emitARM64TAC("eor", operands, :ptr)
+ when "xorq"
+ emitARM64TAC("eor", operands, :ptr)
+ when "lshifti"
+ emitARM64Shift("lslv", "ubfm", operands, :int) {
+ | value |
+ [32 - value, 31 - value]
+ }
+ when "lshiftp"
+ emitARM64Shift("lslv", "ubfm", operands, :ptr) {
+ | value |
+ [64 - value, 63 - value]
+ }
+ when "lshiftq"
+ emitARM64Shift("lslv", "ubfm", operands, :ptr) {
+ | value |
+ [64 - value, 63 - value]
+ }
+ when "rshifti"
+ emitARM64Shift("asrv", "sbfm", operands, :int) {
+ | value |
+ [value, 31]
+ }
+ when "rshiftp"
+ emitARM64Shift("asrv", "sbfm", operands, :ptr) {
+ | value |
+ [value, 63]
+ }
+ when "rshiftq"
+ emitARM64Shift("asrv", "sbfm", operands, :ptr) {
+ | value |
+ [value, 63]
+ }
+ when "urshifti"
+ emitARM64Shift("lsrv", "ubfm", operands, :int) {
+ | value |
+ [value, 31]
+ }
+ when "urshiftp"
+ emitARM64Shift("lsrv", "ubfm", operands, :ptr) {
+ | value |
+ [value, 63]
+ }
+ when "urshiftq"
+ emitARM64Shift("lsrv", "ubfm", operands, :ptr) {
+ | value |
+ [value, 63]
+ }
+ when "muli"
+ $asm.puts "madd #{arm64TACOperands(operands, :int)}, wzr"
+ when "mulp"
+ $asm.puts "madd #{arm64TACOperands(operands, :ptr)}, xzr"
+ when "mulq"
+ $asm.puts "madd #{arm64TACOperands(operands, :ptr)}, xzr"
+ when "subi"
+ emitARM64TAC("sub", operands, :int)
+ when "subp"
+ emitARM64TAC("sub", operands, :ptr)
+ when "subq"
+ emitARM64TAC("sub", operands, :ptr)
+ when "subis"
+ emitARM64TAC("subs", operands, :int)
+ when "negi"
+ $asm.puts "sub #{operands[0].arm64Operand(:int)}, wzr, #{operands[0].arm64Operand(:int)}"
+ when "negp"
+ $asm.puts "sub #{operands[0].arm64Operand(:ptr)}, xzr, #{operands[0].arm64Operand(:ptr)}"
+ when "negq"
+ $asm.puts "sub #{operands[0].arm64Operand(:ptr)}, xzr, #{operands[0].arm64Operand(:ptr)}"
+ when "loadi"
+ emitARM64Access("ldr", "ldur", operands[1], operands[0], :int)
+ when "loadis"
+ emitARM64Access("ldrsw", "ldursw", operands[1], operands[0], :ptr)
+ when "loadp"
+ emitARM64Access("ldr", "ldur", operands[1], operands[0], :ptr)
+ when "loadq"
+ emitARM64Access("ldr", "ldur", operands[1], operands[0], :ptr)
+ when "storei"
+ emitARM64Unflipped("str", operands, :int)
+ when "storep"
+ emitARM64Unflipped("str", operands, :ptr)
+ when "storeq"
+ emitARM64Unflipped("str", operands, :ptr)
+ when "loadb"
+ emitARM64Access("ldrb", "ldurb", operands[1], operands[0], :int)
+ when "loadbs"
+ emitARM64Access("ldrsb", "ldursb", operands[1], operands[0], :int)
+ when "storeb"
+ emitARM64Unflipped("strb", operands, :int)
+ when "loadh"
+ emitARM64Access("ldrh", "ldurh", operands[1], operands[0], :int)
+ when "loadhs"
+ emitARM64Access("ldrsh", "ldursh", operands[1], operands[0], :int)
+ when "storeh"
+ emitARM64Unflipped("strh", operands, :int)
+ when "loadd"
+ emitARM64Access("ldr", "ldur", operands[1], operands[0], :double)
+ when "stored"
+ emitARM64Unflipped("str", operands, :double)
+ when "addd"
+ emitARM64TAC("fadd", operands, :double)
+ when "divd"
+ emitARM64TAC("fdiv", operands, :double)
+ when "subd"
+ emitARM64TAC("fsub", operands, :double)
+ when "muld"
+ emitARM64TAC("fmul", operands, :double)
+ when "sqrtd"
+ emitARM64("fsqrt", operands, :double)
+ when "ci2d"
+ emitARM64("scvtf", operands, [:int, :double])
+ when "bdeq"
+ emitARM64Branch("fcmp", operands, :double, "b.eq")
+ when "bdneq"
+ emitARM64Unflipped("fcmp", operands[0..1], :double)
+ isUnordered = LocalLabel.unique("bdneq")
+ $asm.puts "b.vs #{LocalLabelReference.new(codeOrigin, isUnordered).asmLabel}"
+ $asm.puts "b.ne #{operands[2].asmLabel}"
+ isUnordered.lower("ARM64")
+ when "bdgt"
+ emitARM64Branch("fcmp", operands, :double, "b.gt")
+ when "bdgteq"
+ emitARM64Branch("fcmp", operands, :double, "b.ge")
+ when "bdlt"
+ emitARM64Branch("fcmp", operands, :double, "b.mi")
+ when "bdlteq"
+ emitARM64Branch("fcmp", operands, :double, "b.ls")
+ when "bdequn"
+ emitARM64Unflipped("fcmp", operands[0..1], :double)
+ $asm.puts "b.vs #{operands[2].asmLabel}"
+ $asm.puts "b.eq #{operands[2].asmLabel}"
+ when "bdnequn"
+ emitARM64Branch("fcmp", operands, :double, "b.ne")
+ when "bdgtun"
+ emitARM64Branch("fcmp", operands, :double, "b.hi")
+ when "bdgtequn"
+ emitARM64Branch("fcmp", operands, :double, "b.pl")
+ when "bdltun"
+ emitARM64Branch("fcmp", operands, :double, "b.lt")
+ when "bdltequn"
+ emitARM64Branch("fcmp", operands, :double, "b.le")
+ when "btd2i"
+ # FIXME: may be a good idea to just get rid of this instruction, since the interpreter
+ # currently does not use it.
+ raise "ARM64 does not support this opcode yet, #{codeOriginString}"
+ when "td2i"
+ emitARM64("fcvtzs", operands, [:double, :int])
+ when "bcd2i"
+ # FIXME: remove this instruction, or use it and implement it. Currently it's not
+ # used.
+ raise "ARM64 does not support this opcode yet, #{codeOriginString}"
+ when "movdz"
+ # FIXME: remove it or support it.
+ raise "ARM64 does not support this opcode yet, #{codeOriginString}"
+ when "pop"
+ emitARM64Unflipped("pop", operands, :ptr)
+ when "push"
+ emitARM64Unflipped("push", operands, :ptr)
+ when "move"
+ if operands[0].immediate?
+ emitARM64MoveImmediate(operands[0].value, operands[1])
+ else
+ emitARM64("mov", operands, :ptr)
+ end
+ when "sxi2p"
+ emitARM64("sxtw", operands, :ptr)
+ when "sxi2q"
+ emitARM64("sxtw", operands, :ptr)
+ when "zxi2p"
+ emitARM64("uxtw", operands, :ptr)
+ when "zxi2q"
+ emitARM64("uxtw", operands, :ptr)
+ when "nop"
+ $asm.puts "nop"
+ when "bieq", "bbeq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbz #{operands[1].arm64Operand(:int)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbz #{operands[0].arm64Operand(:int)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs wzr, ", operands, :int, "b.eq")
+ end
+ when "bpeq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbz #{operands[1].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbz #{operands[0].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.eq")
+ end
+ when "bqeq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbz #{operands[1].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbz #{operands[0].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.eq")
+ end
+ when "bineq", "bbneq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbnz #{operands[1].arm64Operand(:int)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbnz #{operands[0].arm64Operand(:int)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs wzr, ", operands, :int, "b.ne")
+ end
+ when "bpneq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbnz #{operands[1].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbnz #{operands[0].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ne")
+ end
+ when "bqneq"
+ if operands[0].immediate? and operands[0].value == 0
+ $asm.puts "cbnz #{operands[1].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ elsif operands[1].immediate? and operands[1].value == 0
+ $asm.puts "cbnz #{operands[0].arm64Operand(:ptr)}, #{operands[2].asmLabel}"
+ else
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ne")
+ end
+ when "bia", "bba"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.hi")
+ when "bpa"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.hi")
+ when "bqa"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.hi")
+ when "biaeq", "bbaeq"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.hs")
+ when "bpaeq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.hs")
+ when "bqaeq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.hs")
+ when "bib", "bbb"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.lo")
+ when "bpb"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.lo")
+ when "bqb"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.lo")
+ when "bibeq", "bbbeq"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.ls")
+ when "bpbeq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ls")
+ when "bqbeq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ls")
+ when "bigt", "bbgt"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.gt")
+ when "bpgt"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.gt")
+ when "bqgt"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.gt")
+ when "bigteq", "bbgteq"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.ge")
+ when "bpgteq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ge")
+ when "bqgteq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.ge")
+ when "bilt", "bblt"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.lt")
+ when "bplt"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.lt")
+ when "bqlt"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.lt")
+ when "bilteq", "bblteq"
+ emitARM64Branch("subs wzr, ", operands, :int, "b.le")
+ when "bplteq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.le")
+ when "bqlteq"
+ emitARM64Branch("subs xzr, ", operands, :ptr, "b.le")
+ when "jmp"
+ if operands[0].label?
+ $asm.puts "b #{operands[0].asmLabel}"
+ else
+ emitARM64Unflipped("br", operands, :ptr)
+ end
+ when "call"
+ if operands[0].label?
+ $asm.puts "bl #{operands[0].asmLabel}"
+ else
+ emitARM64Unflipped("blr", operands, :ptr)
+ end
+ when "break"
+ $asm.puts "brk \#0"
+ when "ret"
+ $asm.puts "ret"
+ when "cieq", "cbeq"
+ emitARM64Compare(operands, :int, "ne")
+ when "cpeq"
+ emitARM64Compare(operands, :ptr, "ne")
+ when "cqeq"
+ emitARM64Compare(operands, :ptr, "ne")
+ when "cineq", "cbneq"
+ emitARM64Compare(operands, :int, "eq")
+ when "cpneq"
+ emitARM64Compare(operands, :ptr, "eq")
+ when "cqneq"
+ emitARM64Compare(operands, :ptr, "eq")
+ when "cia", "cba"
+ emitARM64Compare(operands, :int, "ls")
+ when "cpa"
+ emitARM64Compare(operands, :ptr, "ls")
+ when "cqa"
+ emitARM64Compare(operands, :ptr, "ls")
+ when "ciaeq", "cbaeq"
+ emitARM64Compare(operands, :int, "lo")
+ when "cpaeq"
+ emitARM64Compare(operands, :ptr, "lo")
+ when "cqaeq"
+ emitARM64Compare(operands, :ptr, "lo")
+ when "cib", "cbb"
+ emitARM64Compare(operands, :int, "hs")
+ when "cpb"
+ emitARM64Compare(operands, :ptr, "hs")
+ when "cqb"
+ emitARM64Compare(operands, :ptr, "hs")
+ when "cibeq", "cbbeq"
+ emitARM64Compare(operands, :int, "hi")
+ when "cpbeq"
+ emitARM64Compare(operands, :ptr, "hi")
+ when "cqbeq"
+ emitARM64Compare(operands, :ptr, "hi")
+ when "cilt", "cblt"
+ emitARM64Compare(operands, :int, "ge")
+ when "cplt"
+ emitARM64Compare(operands, :ptr, "ge")
+ when "cqlt"
+ emitARM64Compare(operands, :ptr, "ge")
+ when "cilteq", "cblteq"
+ emitARM64Compare(operands, :int, "gt")
+ when "cplteq"
+ emitARM64Compare(operands, :ptr, "gt")
+ when "cqlteq"
+ emitARM64Compare(operands, :ptr, "gt")
+ when "cigt", "cbgt"
+ emitARM64Compare(operands, :int, "le")
+ when "cpgt"
+ emitARM64Compare(operands, :ptr, "le")
+ when "cqgt"
+ emitARM64Compare(operands, :ptr, "le")
+ when "cigteq", "cbgteq"
+ emitARM64Compare(operands, :int, "lt")
+ when "cpgteq"
+ emitARM64Compare(operands, :ptr, "lt")
+ when "cqgteq"
+ emitARM64Compare(operands, :ptr, "lt")
+ when "peek"
+ $asm.puts "ldr #{operands[1].arm64Operand(:ptr)}, [sp, \##{operands[0].value * 8}]"
+ when "poke"
+ $asm.puts "str #{operands[1].arm64Operand(:ptr)}, [sp, \##{operands[0].value * 8}]"
+ when "fp2d"
+ emitARM64("fmov", operands, [:ptr, :double])
+ when "fq2d"
+ emitARM64("fmov", operands, [:ptr, :double])
+ when "fd2p"
+ emitARM64("fmov", operands, [:double, :ptr])
+ when "fd2q"
+ emitARM64("fmov", operands, [:double, :ptr])
+ when "bo"
+ $asm.puts "b.vs #{operands[0].asmLabel}"
+ when "bs"
+ $asm.puts "b.mi #{operands[0].asmLabel}"
+ when "bz"
+ $asm.puts "b.eq #{operands[0].asmLabel}"
+ when "bnz"
+ $asm.puts "b.ne #{operands[0].asmLabel}"
+ when "leai"
+ operands[0].arm64EmitLea(operands[1], :int)
+ when "leap"
+ operands[0].arm64EmitLea(operands[1], :ptr)
+ when "leaq"
+ operands[0].arm64EmitLea(operands[1], :ptr)
+ when "smulli"
+ $asm.puts "smaddl #{operands[2].arm64Operand(:ptr)}, #{operands[0].arm64Operand(:int)}, #{operands[1].arm64Operand(:int)}, xzr"
+ else
+ raise "Unhandled opcode #{opcode} at #{codeOriginString}"
+ end
+ end
+end
+