blob: 75f0526cfaaec024a48ac775571fa571395872bb [file] [log] [blame]
# Copyright (C) 2012-2018 Apple Inc. All rights reserved.
# Copyright (C) 2012 MIPS Technologies, 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 MIPS TECHNOLOGIES, 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 MIPS TECHNOLOGIES, INC. OR
# 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'
# GPR conventions, to match the baseline JIT
#
# $a0 => a0, t7
# $a1 => a1, t8
# $a2 => a2, t9
# $a3 => a3, t10
# $v0 => t0, r0
# $v1 => t1, r1
# $t0 => (scratch)
# $t1 => (scratch)
# $t2 => t2
# $t3 => t3
# $t4 => t4
# $t5 => t5
# $t6 => t6
# $t7 => (scratch)
# $t8 => (scratch)
# $t9 => (stores the callee of a call opcode)
# $gp => (globals)
# $s0 => csr0 (callee-save, metadataTable)
# $s1 => csr1 (callee-save, PB)
# $s4 => (callee-save used to preserve $gp across calls)
# $ra => lr
# $sp => sp
# $fp => cfr
#
# FPR conventions, to match the baseline JIT
# We don't have fa2 or fa3!
# $f0 => ft0, fr
# $f2 => ft1
# $f4 => ft2
# $f6 => ft3
# $f8 => ft4
# $f10 => ft5
# $f12 => fa0
# $f14 => fa1
# $f16 => (scratch)
# $f18 => (scratch)
class Assembler
def putStr(str)
@outp.puts str
end
end
class Node
def mipsSingleHi
doubleOperand = mipsOperand
raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^\$f/
"$f" + ($~.post_match.to_i + 1).to_s
end
def mipsSingleLo
doubleOperand = mipsOperand
raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^\$f/
doubleOperand
end
end
class SpecialRegister < NoChildren
def mipsOperand
@name
end
def dump
@name
end
def register?
true
end
end
MIPS_TEMP_GPRS = [SpecialRegister.new("$t0"), SpecialRegister.new("$t1"), SpecialRegister.new("$t7"), SpecialRegister.new("$t8")]
MIPS_ZERO_REG = SpecialRegister.new("$zero")
MIPS_GP_REG = SpecialRegister.new("$gp")
MIPS_GPSAVE_REG = SpecialRegister.new("$s4")
MIPS_CALL_REG = SpecialRegister.new("$t9")
MIPS_RETURN_ADDRESS_REG = SpecialRegister.new("$ra")
MIPS_TEMP_FPRS = [SpecialRegister.new("$f16")]
MIPS_SCRATCH_FPR = SpecialRegister.new("$f18")
def mipsMoveImmediate(value, register)
if value == 0
$asm.puts "add #{register.mipsOperand}, $zero, $zero"
else
$asm.puts "li #{register.mipsOperand}, #{value}"
end
end
class RegisterID
def mipsOperand
case name
when "a0", "t7"
"$a0"
when "a1", "t8"
"$a1"
when "a2", "t9"
"$a2"
when "a3", "t10"
"$a3"
when "t0", "r0"
"$v0"
when "t1", "r1"
"$v1"
when "t2"
"$t2"
when "t3"
"$t3"
when "t4"
"$t4"
when "t5"
"$t5"
when "cfr"
"$fp"
when "csr0"
"$s0"
when "csr1"
"$s1"
when "lr"
"$ra"
when "sp"
"$sp"
else
raise "Bad register #{name} for MIPS at #{codeOriginString}"
end
end
end
class FPRegisterID
def mipsOperand
case name
when "ft0", "fr"
"$f0"
when "ft1"
"$f2"
when "ft2"
"$f4"
when "ft3"
"$f6"
when "ft4"
"$f8"
when "ft5"
"$f10"
when "fa0"
"$f12"
when "fa1"
"$f14"
else
raise "Bad register #{name} for MIPS at #{codeOriginString}"
end
end
end
class Immediate
def mipsOperand
raise "Invalid immediate #{value} at #{codeOriginString}" if value < -0x7fff or value > 0xffff
"#{value}"
end
end
class Address
def mipsOperand
raise "Bad offset at #{codeOriginString}" if offset.value < -0x7fff or offset.value > 0x7fff
"#{offset.value}(#{base.mipsOperand})"
end
end
class AbsoluteAddress
def mipsOperand
raise "Unconverted absolute address at #{codeOriginString}"
end
end
#
# Negate condition of branches to labels.
#
class Instruction
def mipsNegateCondition(list)
/^(b(add|sub|or|mul|t)?)([ipb])/.match(opcode)
case $~.post_match
when "eq"
op = "neq"
when "neq"
op = "eq"
when "z"
op = "nz"
when "nz"
op = "z"
when "gt"
op = "lteq"
when "gteq"
op = "lt"
when "lt"
op = "gteq"
when "lteq"
op = "gt"
when "a"
op = "beq"
when "b"
op = "aeq"
when "aeq"
op = "b"
when "beq"
op = "a"
else
raise "Can't negate #{opcode} branch."
end
noBranch = LocalLabel.unique("nobranch")
noBranchRef = LocalLabelReference.new(codeOrigin, noBranch)
toRef = operands[-1]
list << Instruction.new(codeOrigin, "#{$1}#{$3}#{op}", operands[0..-2].push(noBranchRef), annotation)
list << Instruction.new(codeOrigin, "la", [toRef, MIPS_CALL_REG])
list << Instruction.new(codeOrigin, "jmp", [MIPS_CALL_REG])
list << noBranch
end
end
def mipsLowerFarBranchOps(list)
newList = []
list.each {
| node |
if node.is_a? Instruction
annotation = node.annotation
case node.opcode
when /^b(add|sub|or|mul|t)?([ipb])/
if node.operands[-1].is_a? LabelReference
node.mipsNegateCondition(newList)
next
end
end
end
newList << node
}
newList
end
#
# Lower 'and' masked branches
#
def lowerMIPSCondBranch(list, condOp, node)
if node.operands.size == 2
list << Instruction.new(node.codeOrigin,
condOp,
[node.operands[0], MIPS_ZERO_REG, node.operands[-1]],
node.annotation)
elsif node.operands.size == 3
tl = condOp[-1, 1]
tmp = Tmp.new(node.codeOrigin, :gpr)
list << Instruction.new(node.codeOrigin,
"and" + tl,
[node.operands[0], node.operands[1], tmp],
node.annotation)
list << Instruction.new(node.codeOrigin,
condOp,
[tmp, MIPS_ZERO_REG, node.operands[-1]])
else
raise "Expected 2 or 3 operands but got #{node.operands.size} at #{node.codeOriginString}"
end
end
#
# Lowering of branch ops. For example:
#
# baddiz foo, bar, baz
#
# will become:
#
# addi foo, bar
# bz baz
#
def mipsLowerSimpleBranchOps(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
branch = "b" + bc
case op
when "addi", "addp"
op = "addi"
when "subi"
op = "subi"
when "ori"
op = "ori"
end
if bc == "o"
case op
when "addi"
# addu $s0, $s1, $s2
# xor $t0, $s1, $s2
# blt $t0, $zero, no overflow
# xor $t0, $s0, $s1
# blt $t0, $zero, overflow
# no overflow:
#
tr = Tmp.new(node.codeOrigin, :gpr)
tmp = Tmp.new(node.codeOrigin, :gpr)
noFlow = LocalLabel.unique("noflow")
noFlowRef = LocalLabelReference.new(node.codeOrigin, noFlow)
newList << Instruction.new(node.codeOrigin, op, [node.operands[0], node.operands[1], tr], annotation)
newList << Instruction.new(node.codeOrigin, "xori", [node.operands[0], node.operands[1], tmp])
newList << Instruction.new(node.codeOrigin, "bilt", [tmp, MIPS_ZERO_REG, noFlowRef])
newList << Instruction.new(node.codeOrigin, "xori", [tr, node.operands[0], tmp])
newList << Instruction.new(node.codeOrigin, "bilt", [tmp, MIPS_ZERO_REG, node.operands[2]])
newList << noFlow
newList << Instruction.new(node.codeOrigin, "move", [tr, node.operands[1]])
when "subi"
# subu $s0, $s1, $s2
# xor $t0, $s1, $s2
# bge $t0, $zero, no overflow
# xor $t0, $s0, $s1
# blt $t0, $zero, overflow
# no overflow:
#
tr = Tmp.new(node.codeOrigin, :gpr)
tmp = Tmp.new(node.codeOrigin, :gpr)
noFlow = LocalLabel.unique("noflow")
noFlowRef = LocalLabelReference.new(node.codeOrigin, noFlow)
newList << Instruction.new(node.codeOrigin, op, [node.operands[1], node.operands[0], tr], annotation)
newList << Instruction.new(node.codeOrigin, "xori", [node.operands[1], node.operands[0], tmp])
newList << Instruction.new(node.codeOrigin, "bigteq", [tmp, MIPS_ZERO_REG, noFlowRef])
newList << Instruction.new(node.codeOrigin, "xori", [tr, node.operands[1], tmp])
newList << Instruction.new(node.codeOrigin, "bilt", [tmp, MIPS_ZERO_REG, node.operands[2]])
newList << noFlow
newList << Instruction.new(node.codeOrigin, "move", [tr, node.operands[1]])
when "ori"
# no ovwerflow at ori
newList << Instruction.new(node.codeOrigin, op, node.operands[0..1], annotation)
end
else
if node.operands[1].is_a? Address
addr = node.operands[1]
tr = Tmp.new(node.codeOrigin, :gpr)
newList << Instruction.new(node.codeOrigin, "loadp", [addr, tr], annotation)
newList << Instruction.new(node.codeOrigin, op, [node.operands[0], tr])
newList << Instruction.new(node.codeOrigin, "storep", [tr, addr])
else
tr = node.operands[1]
newList << Instruction.new(node.codeOrigin, op, node.operands[0..-2], annotation)
end
newList << Instruction.new(node.codeOrigin, branch, [tr, MIPS_ZERO_REG, node.operands[-1]])
end
when "bia", "bpa", "bba"
tmp = Tmp.new(node.codeOrigin, :gpr)
comp = node.opcode[1] == ?b ? "sltub" : "sltu"
newList << Instruction.new(node.codeOrigin, comp, [tmp, node.operands[1], node.operands[0]], annotation)
newList << Instruction.new(node.codeOrigin, "bnz", [tmp, MIPS_ZERO_REG, node.operands[2]])
when "biaeq", "bpaeq", "bbaeq"
tmp = Tmp.new(node.codeOrigin, :gpr)
comp = node.opcode[1] == ?b ? "sltub" : "sltu"
newList << Instruction.new(node.codeOrigin, comp, [tmp, node.operands[0], node.operands[1]], annotation)
newList << Instruction.new(node.codeOrigin, "bz", [tmp, MIPS_ZERO_REG, node.operands[2]])
when "bib", "bpb", "bbb"
tmp = Tmp.new(node.codeOrigin, :gpr)
comp = node.opcode[1] == ?b ? "sltub" : "sltu"
newList << Instruction.new(node.codeOrigin, comp, [tmp, node.operands[0], node.operands[1]], annotation)
newList << Instruction.new(node.codeOrigin, "bnz", [tmp, MIPS_ZERO_REG, node.operands[2]])
when "bibeq", "bpbeq", "bbbeq"
tmp = Tmp.new(node.codeOrigin, :gpr)
comp = node.opcode[1] == ?b ? "sltub" : "sltu"
newList << Instruction.new(node.codeOrigin, comp, [tmp, node.operands[1], node.operands[0]], annotation)
newList << Instruction.new(node.codeOrigin, "bz", [tmp, MIPS_ZERO_REG, node.operands[2]])
when /^bt(i|p|b)/
lowerMIPSCondBranch(newList, "b" + $~.post_match + $1, node)
else
newList << node
end
else
newList << node
end
}
newList
end
#
# Specialization of lowering of malformed BaseIndex addresses.
#
class Node
def mipsLowerMalformedAddressesRecurse(list)
mapChildren {
| subNode |
subNode.mipsLowerMalformedAddressesRecurse(list)
}
end
def mipsLowerShiftedAddressesRecurse(list, isFirst, tmp)
mapChildren {
| subNode |
subNode.mipsLowerShiftedAddressesRecurse(list, isFirst, tmp)
}
end
end
class BaseIndex
def mipsLowerMalformedAddressesRecurse(list)
tmp = Tmp.new(codeOrigin, :gpr)
if scaleShift == 0
list << Instruction.new(codeOrigin, "addp", [base, index, tmp])
Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, offset.value));
end
end
def mipsLowerShiftedAddressesRecurse(list, isFirst, tmp)
if isFirst
list << Instruction.new(codeOrigin, "lshifti", [index, Immediate.new(codeOrigin, scaleShift), tmp]);
list << Instruction.new(codeOrigin, "addp", [base, tmp])
end
Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, offset.value));
end
end
#
# Lowering of BaseIndex addresses with optimization for MIPS.
#
# offline asm instruction pair:
# loadi 4[cfr, t0, 8], t2
# loadi 0[cfr, t0, 8], t0
#
# lowered instructions:
# lshifti t0, 3, tmp
# addp cfr, tmp
# loadi 4[tmp], t2
# loadi 0[tmp], t0
#
def mipsHasShiftedBaseIndexAddress(instruction)
instruction.operands.each_with_index {
| operand, index |
if operand.is_a? BaseIndex and operand.scaleShift != 0
return index
end
}
-1
end
def mipsScaleOfBaseIndexMatches(baseIndex0, baseIndex1)
baseIndex0.base == baseIndex1.base and
baseIndex0.index == baseIndex1.index and
baseIndex0.scale == baseIndex1.scale
end
def mipsLowerBaseIndexAddresses(list)
newList = [ list[0] ]
tmp = nil
list.each_cons(2) {
| nodes |
if nodes[1].is_a? Instruction
ind = mipsHasShiftedBaseIndexAddress(nodes[1])
if ind != -1
if nodes[0].is_a? Instruction and
nodes[0].opcode == nodes[1].opcode and
ind == mipsHasShiftedBaseIndexAddress(nodes[0]) and
mipsScaleOfBaseIndexMatches(nodes[0].operands[ind], nodes[1].operands[ind])
newList << nodes[1].mipsLowerShiftedAddressesRecurse(newList, false, tmp)
else
tmp = Tmp.new(codeOrigin, :gpr)
newList << nodes[1].mipsLowerShiftedAddressesRecurse(newList, true, tmp)
end
else
newList << nodes[1].mipsLowerMalformedAddressesRecurse(newList)
end
else
newList << nodes[1]
end
}
newList
end
#
# Lowering of misplaced immediates of MIPS specific instructions. For example:
#
# sltu reg, 4, 2
#
# will become:
#
# move 4, tmp
# sltu reg, tmp, 2
#
def mipsLowerMisplacedImmediates(list)
newList = []
list.each {
| node |
if node.is_a? Instruction
case node.opcode
when "slt", "sltu", "sltb", "sltub"
if node.operands[1].is_a? Immediate
tmp = Tmp.new(node.codeOrigin, :gpr)
newList << Instruction.new(node.codeOrigin, "move", [node.operands[1], tmp], node.annotation)
newList << Instruction.new(node.codeOrigin, node.opcode,
[node.operands[0], tmp, node.operands[2]],
node.annotation)
else
newList << node
end
when /^(addi|subi)/
newList << node.riscLowerMalformedImmediatesRecurse(newList, -0x7fff..0x7fff)
when "andi", "andp", "ori", "orp", "orh", "xori", "xorp"
newList << node.riscLowerMalformedImmediatesRecurse(newList, 0..0xffff)
else
newList << node
end
else
newList << node
end
}
newList
end
#
# Specialization of lowering of misplaced addresses.
#
class LocalLabelReference
def register?
false
end
end
def mipsAsRegister(preList, postList, operand, needRestore)
tmp = MIPS_CALL_REG
if operand.address?
preList << Instruction.new(operand.codeOrigin, "loadp", [operand, MIPS_CALL_REG])
elsif operand.is_a? LabelReference
preList << Instruction.new(operand.codeOrigin, "la", [operand, MIPS_CALL_REG])
elsif operand.register? and operand != MIPS_CALL_REG
preList << Instruction.new(operand.codeOrigin, "move", [operand, MIPS_CALL_REG])
else
needRestore = false
tmp = operand
end
if needRestore
postList << Instruction.new(operand.codeOrigin, "move", [MIPS_GPSAVE_REG, MIPS_GP_REG])
end
tmp
end
def mipsLowerMisplacedAddresses(list)
newList = []
list.each {
| node |
if node.is_a? Instruction
postInstructions = []
annotation = node.annotation
case node.opcode
when "jmp"
newList << Instruction.new(node.codeOrigin,
node.opcode,
[mipsAsRegister(newList, [], node.operands[0], false)])
when "call"
newList << Instruction.new(node.codeOrigin,
node.opcode,
[mipsAsRegister(newList, postInstructions, node.operands[0], true)])
when "slt", "sltu"
newList << Instruction.new(node.codeOrigin,
node.opcode,
riscAsRegisters(newList, [], node.operands, "i"))
when "sltub", "sltb"
newList << Instruction.new(node.codeOrigin,
node.opcode,
riscAsRegisters(newList, [], node.operands, "b"))
when "andb"
newList << Instruction.new(node.codeOrigin,
"andi",
riscAsRegisters(newList, [], node.operands, "b"))
when /^(bz|bnz|bs|bo)/
tl = $~.post_match == "" ? "i" : $~.post_match
newList << Instruction.new(node.codeOrigin,
node.opcode,
riscAsRegisters(newList, [], node.operands, tl))
else
newList << node
end
newList += postInstructions
else
newList << node
end
}
newList
end
#
# Lowering compares and tests.
#
def mipsLowerCompareTemplate(list, node, opCmp, opMov)
tmp0 = Tmp.new(node.codeOrigin, :gpr)
tmp1 = Tmp.new(node.codeOrigin, :gpr)
list << Instruction.new(node.codeOrigin, "move", [Immediate.new(nil, 0), node.operands[2]])
list << Instruction.new(node.codeOrigin, opCmp, [node.operands[1], node.operands[0], tmp0])
list << Instruction.new(node.codeOrigin, "move", [Immediate.new(nil, 1), tmp1])
list << Instruction.new(node.codeOrigin, opMov, [node.operands[2], tmp1, tmp0])
end
def mipsLowerCompares(list)
newList = []
list.each {
| node |
if node.is_a? Instruction
case node.opcode
when "cieq", "cpeq", "cbeq"
mipsLowerCompareTemplate(newList, node, "subp", "movz")
when "cineq", "cpneq", "cbneq"
mipsLowerCompareTemplate(newList, node, "subp", "movn")
when "tiz", "tbz", "tpz"
mipsLowerCompareTemplate(newList, node, "andp", "movz")
when "tinz", "tbnz", "tpnz"
mipsLowerCompareTemplate(newList, node, "andp", "movn")
when "tio", "tbo", "tpo"
tmp = Tmp.new(node.codeOrigin, :gpr)
list << Instruction.new(node.codeOrigin, "andp", [node.operands[1], node.operands[0], tmp])
list << Instruction.new(node.codeOrigin, "slt", [node.operands[2], MIPS_ZERO_REG, tmp])
when "tis", "tbs", "tps"
tmp = Tmp.new(node.codeOrigin, :gpr)
list << Instruction.new(node.codeOrigin, "andp", [node.operands[1], node.operands[0], tmp])
list << Instruction.new(node.codeOrigin, "slt", [node.operands[2], tmp, MIPS_ZERO_REG])
else
newList << node
end
else
newList << node
end
}
newList
end
#
# Lea support.
#
class Address
def mipsEmitLea(destination)
if destination == base
$asm.puts "addiu #{destination.mipsOperand}, #{offset.value}"
else
$asm.puts "addiu #{destination.mipsOperand}, #{base.mipsOperand}, #{offset.value}"
end
end
end
#
# Add PIC compatible header code to all the LLInt rutins.
#
def mipsAddPICCode(list)
myList = []
list.each {
| node |
myList << node
if node.is_a? Label
# FIXME: [JSC] checkpoint_osr_exit_from_inlined_call_trampoline is a return location
# and we should name it properly.
# https://bugs.webkit.org/show_bug.cgi?id=208236
if node.name =~ /^.*_return_location(?:_(?:wide16|wide32))?$/ or node.name.start_with?("_checkpoint_osr_exit_from_inlined_call_trampoline")
# We need to have a special case for return location labels because they are always
# reached from a `ret` instruction. In this case, we need to proper reconfigure `$gp`
# using `$ra` instead of using `$t9`.
myList << Instruction.new(node.codeOrigin, "pichdr", [MIPS_RETURN_ADDRESS_REG])
else
myList << Instruction.new(node.codeOrigin, "pichdr", [MIPS_CALL_REG])
end
end
}
myList
end
#
# Actual lowering code follows.
#
class Sequence
def getModifiedListMIPS
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 = mipsAddPICCode(result)
result = mipsLowerFarBranchOps(result)
result = mipsLowerSimpleBranchOps(result)
result = riscLowerSimpleBranchOps(result)
result = riscLowerHardBranchOps(result)
result = riscLowerShiftOps(result)
result = mipsLowerBaseIndexAddresses(result)
result = riscLowerMalformedAddresses(result) {
| node, address |
if address.is_a? Address
(-0x7fff..0x7fff).include? address.offset.value
else
false
end
}
result = riscLowerMalformedAddressesDouble(result)
result = riscLowerMisplacedImmediates(result, ["storeb", "storei", "storep"])
result = mipsLowerMisplacedImmediates(result)
result = riscLowerMalformedImmediates(result, -0x7fff..0x7fff, -0x7fff..0x7fff)
result = mipsLowerMisplacedAddresses(result)
result = riscLowerMisplacedAddresses(result)
result = riscLowerRegisterReuse(result)
result = mipsLowerCompares(result)
result = assignRegistersToTemporaries(result, :gpr, MIPS_TEMP_GPRS)
result = assignRegistersToTemporaries(result, :fpr, MIPS_TEMP_FPRS)
return result
end
end
def mipsOperands(operands)
operands.map{|v| v.mipsOperand}.join(", ")
end
def mipsFlippedOperands(operands)
mipsOperands([operands[-1]] + operands[0..-2])
end
def getMIPSOpcode(opcode, suffix)
end
def emitMIPSCompact(opcode, opcodei, operands)
postfix = ""
if opcode == "sub"
if operands[0].is_a? Immediate
opcode = "add"
operands[0] = Immediate.new(operands[0].codeOrigin, -1 * operands[0].value)
elsif operands[1].is_a? Immediate
opcode = "add"
operands[1] = Immediate.new(operands[1].codeOrigin, -1 * operands[1].value)
end
postfix = "u"
elsif opcode == "add"
postfix = "u"
end
if operands.size == 3
if operands[0].is_a? Immediate
$asm.puts "#{opcode}i#{postfix} #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].value}"
elsif operands[1].is_a? Immediate
$asm.puts "#{opcode}i#{postfix} #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].value}"
else
$asm.puts "#{opcode}#{postfix} #{mipsFlippedOperands(operands)}"
end
else
raise unless operands.size == 2
raise unless operands[1].register?
if operands[0].is_a? Immediate
$asm.puts "#{opcode}i#{postfix} #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
else
$asm.puts "#{opcode}#{postfix} #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
end
end
end
def emitMIPSShiftCompact(opcode, operands)
if operands.size == 3
if (operands[1].is_a? Immediate)
$asm.puts "#{opcode} #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].value}"
else
$asm.puts "#{opcode}v #{mipsFlippedOperands(operands)}"
end
else
raise unless operands.size == 2
if operands[0].register?
$asm.puts "#{opcode}v #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
else
$asm.puts "#{opcode} #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].value}"
end
end
end
def emitMIPS(opcode, operands)
if operands.size == 3
$asm.puts "#{opcode} #{mipsFlippedOperands(operands)}"
else
raise unless operands.size == 2
$asm.puts "#{opcode} #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
end
end
def emitMIPSDoubleBranch(branchOpcode, neg, operands)
$asm.puts "c.#{branchOpcode}.d #{mipsOperands(operands[0..1])}"
if (!neg)
$asm.puts "bc1t #{operands[2].asmLabel}"
else
$asm.puts "bc1f #{operands[2].asmLabel}"
end
end
def emitMIPSJumpOrCall(opcode, operand)
if operand.label?
raise "Direct call/jump to a not local label." unless operand.is_a? LocalLabelReference
$asm.puts "#{opcode} #{operand.asmLabel}"
else
raise "Invalid call/jump register." unless operand == MIPS_CALL_REG
$asm.puts "#{opcode}r #{MIPS_CALL_REG.mipsOperand}"
end
end
class Instruction
def lowerMIPS
case opcode
when "addi", "addp", "addis"
if operands.size == 3 and operands[0].is_a? Immediate
raise unless operands[1].register?
raise unless operands[2].register?
if operands[0].value == 0 #and suffix.empty?
unless operands[1] == operands[2]
$asm.puts "move #{operands[2].mipsOperand}, #{operands[1].mipsOperand}"
end
else
$asm.puts "addiu #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
end
elsif operands.size == 3 and operands[0].register?
raise unless operands[1].register?
raise unless operands[2].register?
$asm.puts "addu #{mipsFlippedOperands(operands)}"
else
if operands[0].is_a? Immediate
unless Immediate.new(nil, 0) == operands[0]
$asm.puts "addiu #{operands[1].mipsOperand}, #{mipsFlippedOperands(operands)}"
end
else
$asm.puts "addu #{operands[1].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
end
end
when "andi", "andp"
emitMIPSCompact("and", "and", operands)
when "ori", "orp", "orh"
emitMIPSCompact("or", "orr", operands)
when "oris"
emitMIPSCompact("or", "orrs", operands)
when "xori", "xorp"
emitMIPSCompact("xor", "eor", operands)
when "lshifti", "lshiftp"
emitMIPSShiftCompact("sll", operands)
when "rshifti", "rshiftp"
emitMIPSShiftCompact("sra", operands)
when "urshifti", "urshiftp"
emitMIPSShiftCompact("srl", operands)
when "muli", "mulp"
emitMIPS("mul", operands)
when "subi", "subp", "subis"
emitMIPSCompact("sub", "subs", operands)
when "negi", "negp"
$asm.puts "negu #{operands[0].mipsOperand}, #{operands[0].mipsOperand}"
when "noti"
$asm.puts "nor #{operands[0].mipsOperand}, #{operands[0].mipsOperand}, $zero"
when "loadi", "loadis", "loadp"
$asm.puts "lw #{mipsFlippedOperands(operands)}"
when "storei", "storep"
$asm.puts "sw #{mipsOperands(operands)}"
when "loadb"
$asm.puts "lbu #{mipsFlippedOperands(operands)}"
when "loadbsi"
$asm.puts "lb #{mipsFlippedOperands(operands)}"
when "storeb"
$asm.puts "sb #{mipsOperands(operands)}"
when "loadh"
$asm.puts "lhu #{mipsFlippedOperands(operands)}"
when "loadhsi"
$asm.puts "lh #{mipsFlippedOperands(operands)}"
when "storeh"
$asm.puts "shv #{mipsOperands(operands)}"
when "loadd"
$asm.puts "ldc1 #{mipsFlippedOperands(operands)}"
when "stored"
$asm.puts "sdc1 #{mipsOperands(operands)}"
when "la"
$asm.puts "la #{operands[1].mipsOperand}, #{operands[0].asmLabel}"
when "addd"
emitMIPS("add.d", operands)
when "divd"
emitMIPS("div.d", operands)
when "subd"
emitMIPS("sub.d", operands)
when "muld"
emitMIPS("mul.d", operands)
when "sqrtd"
$asm.puts "sqrt.d #{mipsFlippedOperands(operands)}"
when "ci2ds"
raise "invalid ops of #{self.inspect} at #{codeOriginString}" unless operands[1].is_a? FPRegisterID and operands[0].register?
$asm.puts "mtc1 #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
$asm.puts "cvt.d.w #{operands[1].mipsOperand}, #{operands[1].mipsOperand}"
when "bdeq"
emitMIPSDoubleBranch("eq", false, operands)
when "bdneq"
emitMIPSDoubleBranch("ueq", true, operands)
when "bdgt"
emitMIPSDoubleBranch("ule", true, operands)
when "bdgteq"
emitMIPSDoubleBranch("ult", true, operands)
when "bdlt"
emitMIPSDoubleBranch("olt", false, operands)
when "bdlteq"
emitMIPSDoubleBranch("ole", false, operands)
when "bdequn"
emitMIPSDoubleBranch("ueq", false, operands)
when "bdnequn"
emitMIPSDoubleBranch("eq", true, operands)
when "bdgtun"
emitMIPSDoubleBranch("ole", true, operands)
when "bdgtequn"
emitMIPSDoubleBranch("olt", true, operands)
when "bdltun"
emitMIPSDoubleBranch("ult", false, operands)
when "bdltequn"
emitMIPSDoubleBranch("ule", false, operands)
when "btd2i"
# FIXME: may be a good idea to just get rid of this instruction, since the interpreter
# currently does not use it.
raise "MIPS does not support this opcode yet, #{codeOrigin}"
when "td2i"
$asm.puts "cvt.w.d #{MIPS_SCRATCH_FPR.mipsSingleLo}, #{operands[0].mipsOperand}"
$asm.puts "mfc1 #{operands[1].mipsOperand}, #{MIPS_SCRATCH_FPR.mipsSingleLo}"
when "bcd2i"
$asm.puts "cvt.w.d #{MIPS_SCRATCH_FPR.mipsSingleLo}, #{operands[0].mipsOperand}"
$asm.puts "mfc1 #{operands[1].mipsOperand}, #{MIPS_SCRATCH_FPR.mipsSingleLo}"
$asm.puts "cvt.d.w #{MIPS_SCRATCH_FPR.mipsOperand}, #{MIPS_SCRATCH_FPR.mipsSingleLo}"
emitMIPSDoubleBranch("eq", true, [MIPS_SCRATCH_FPR, operands[0], operands[2]])
$asm.puts "beq #{operands[1].mipsOperand}, $zero, #{operands[2].asmLabel}"
when "movdz"
# FIXME: either support this or remove it.
raise "MIPS does not support this opcode yet, #{codeOrigin}"
when "pop"
operands.each {
| op |
$asm.puts "lw #{op.mipsOperand}, 0($sp)"
$asm.puts "addiu $sp, $sp, 4"
}
when "push"
operands.each {
| op |
$asm.puts "addiu $sp, $sp, -4"
$asm.puts "sw #{op.mipsOperand}, 0($sp)"
}
when "move", "sxi2p", "zxi2p"
if operands[0].is_a? Immediate
mipsMoveImmediate(operands[0].value, operands[1])
else
$asm.puts "move #{mipsFlippedOperands(operands)}"
end
when "nop"
$asm.puts "nop"
when "bieq", "bpeq", "bbeq"
$asm.puts "beq #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "bineq", "bpneq", "bbneq"
$asm.puts "bne #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "bigt", "bpgt", "bbgt"
$asm.puts "bgt #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "bigteq", "bpgteq", "bbgteq"
$asm.puts "bge #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "bilt", "bplt", "bblt"
$asm.puts "blt #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "bilteq", "bplteq", "bblteq"
$asm.puts "ble #{mipsOperands(operands[0..1])}, #{operands[2].asmLabel}"
when "jmp"
emitMIPSJumpOrCall("j", operands[0])
when "call"
emitMIPSJumpOrCall("jal", operands[0])
when "break"
$asm.puts "break"
when "ret"
$asm.puts "jr $ra"
when "cia", "cpa", "cba"
$asm.puts "sltu #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
when "ciaeq", "cpaeq", "cbaeq"
$asm.puts "sltu #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
$asm.puts "xori #{operands[2].mipsOperand}, 1"
when "cib", "cpb", "cbb"
$asm.puts "sltu #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
when "cibeq", "cpbeq", "cbbeq"
$asm.puts "sltu #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
$asm.puts "xori #{operands[2].mipsOperand}, 1"
when "cigt", "cpgt", "cbgt"
$asm.puts "slt #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
when "cigteq", "cpgteq", "cbgteq"
$asm.puts "slt #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
$asm.puts "xori #{operands[2].mipsOperand}, 1"
when "cilt", "cplt", "cblt"
$asm.puts "slt #{operands[2].mipsOperand}, #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
when "cilteq", "cplteq", "cblteq"
$asm.puts "slt #{operands[2].mipsOperand}, #{operands[1].mipsOperand}, #{operands[0].mipsOperand}"
$asm.puts "xori #{operands[2].mipsOperand}, 1"
when "peek"
$asm.puts "lw #{operands[1].mipsOperand}, #{operands[0].value * 4}($sp)"
when "poke"
$asm.puts "sw #{operands[1].mipsOperand}, #{operands[0].value * 4}($sp)"
when "fii2d"
$asm.puts "mtc1 #{operands[0].mipsOperand}, #{operands[2].mipsSingleLo}"
$asm.putStr("#if WTF_MIPS_ISA_REV_AT_LEAST(2)")
$asm.puts "mthc1 #{operands[1].mipsOperand}, #{operands[2].mipsSingleLo}"
$asm.putStr("#else")
$asm.puts "mtc1 #{operands[1].mipsOperand}, #{operands[2].mipsSingleHi}"
$asm.putStr("#endif")
when "fd2ii"
$asm.puts "mfc1 #{operands[1].mipsOperand}, #{operands[0].mipsSingleLo}"
$asm.putStr("#if WTF_MIPS_ISA_REV_AT_LEAST(2)")
$asm.puts "mfhc1 #{operands[2].mipsOperand}, #{operands[0].mipsSingleLo}"
$asm.putStr("#else")
$asm.puts "mfc1 #{operands[2].mipsOperand}, #{operands[0].mipsSingleHi}"
$asm.putStr("#endif")
when /^bo/
$asm.puts "bgt #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].asmLabel}"
when /^bs/
$asm.puts "blt #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].asmLabel}"
when /^bz/
$asm.puts "beq #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].asmLabel}"
when /^bnz/
$asm.puts "bne #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].asmLabel}"
when "leai", "leap"
if operands[0].is_a? LabelReference
labelRef = operands[0]
raise unless labelRef.offset == 0
$asm.puts "lw #{operands[1].mipsOperand}, %got(#{labelRef.asmLabel})($gp)"
else
operands[0].mipsEmitLea(operands[1])
end
when "smulli"
raise "Wrong number of arguments to smull in #{self.inspect} at #{codeOriginString}" unless operands.length == 4
$asm.puts "mult #{operands[0].mipsOperand}, #{operands[1].mipsOperand}"
$asm.puts "mflo #{operands[2].mipsOperand}"
$asm.puts "mfhi #{operands[3].mipsOperand}"
when "movz"
$asm.puts "movz #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].mipsOperand}"
when "movn"
$asm.puts "movn #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].mipsOperand}"
when "setcallreg"
$asm.puts "move #{MIPS_CALL_REG.mipsOperand}, #{operands[0].mipsOperand}"
when "slt", "sltb"
$asm.puts "slt #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].mipsOperand}"
when "sltu", "sltub"
$asm.puts "sltu #{operands[0].mipsOperand}, #{operands[1].mipsOperand}, #{operands[2].mipsOperand}"
when "pichdr"
$asm.putStr("OFFLINE_ASM_CPLOAD(#{operands[0].mipsOperand})")
when "memfence"
$asm.puts "sync"
else
lowerDefault
end
end
end