blob: 6e15483651d2383d9a22cd7cf18dfab54c748db5 [file] [log] [blame]
# Copyright (C) 2012-2021 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 "config"
require "ast"
require "opt"
# The CLoop llint backend is initially based on the ARMv7 backend, and
# then further enhanced with a few instructions from the x86 backend to
# support building for X64 targets. Hence, the shape of the generated
# code and the usage convention of registers will look a lot like the
# ARMv7 backend's.
def cloopMapType(type)
case type
when :intptr; ".i()"
when :uintptr; ".u()"
when :int32; ".i32()"
when :uint32; ".u32()"
when :int64; ".i64()"
when :uint64; ".u64()"
when :int8; ".i8()"
when :uint8; ".u8()"
when :int8Ptr; ".i8p()"
when :voidPtr; ".vp()"
when :nativeFunc; ".nativeFunc()"
when :double; ".d()"
when :bitsAsDouble; ".bitsAsDouble()"
when :bitsAsInt64; ".bitsAsInt64()"
when :opcode; ".opcode()"
else;
raise "Unsupported type"
end
end
class SpecialRegister < NoChildren
def clLValue(type=:intptr)
clDump
end
def clDump
@name
end
def clValue(type=:intptr)
@name + cloopMapType(type)
end
end
C_LOOP_SCRATCH_FPR = SpecialRegister.new("d6")
class RegisterID
def clDump
case name
# The cloop is modelled on the ARM implementation. Hence, the a0-a3
# registers are aliases for r0-r3 i.e. t0-t3 in our case.
when "t0", "a0", "r0"
"t0"
when "t1", "a1", "r1"
"t1"
when "t2", "a2"
"t2"
when "t3", "a3"
"t3"
when "t4"
"pc"
when "t5"
"t5"
when "csr0"
"pcBase"
when "csr1"
"numberTag"
when "csr2"
"notCellMask"
when "csr3"
"metadataTable"
when "cfr"
"cfr"
when "lr"
"lr"
when "sp"
"sp"
else
raise "Bad register #{name} for C_LOOP at #{codeOriginString}"
end
end
def clLValue(type=:intptr)
clDump
end
def clValue(type=:intptr)
clDump + cloopMapType(type)
end
end
class FPRegisterID
def clDump
case name
when "ft0", "fr"
"d0"
when "ft1"
"d1"
when "ft2"
"d2"
when "ft3"
"d3"
when "ft4"
"d4"
when "ft5"
"d5"
else
raise "Bad register #{name} for C_LOOP at #{codeOriginString}"
end
end
def clLValue(type=:intptr)
clDump
end
def clValue(type=:intptr)
clDump + cloopMapType(type)
end
end
class Immediate
def clDump
"#{value}"
end
def clLValue(type=:intptr)
raise "Immediate cannot be used as an LValue"
end
def clValue(type=:intptr)
# There is a case of a very large unsigned number (0x8000000000000000)
# which we wish to encode. Unfortunately, the C/C++ compiler
# complains if we express that number as a positive decimal integer.
# Hence, for positive values, we just convert the number into hex form
# to keep the compiler happy.
#
# However, for negative values, the to_s(16) hex conversion method does
# not strip the "-" sign resulting in a meaningless "0x-..." valueStr.
# To workaround this, we simply don't encode negative numbers as hex.
valueStr = (value < 0) ? "#{value}" : "0x#{value.to_s(16)}"
case type
when :int8; "int8_t(#{valueStr})"
when :int16; "int16_t(#{valueStr})"
when :int32; "int32_t(#{valueStr})"
when :int64; "int64_t(#{valueStr})"
when :intptr; "intptr_t(#{valueStr})"
when :uint8; "uint8_t(#{valueStr})"
when :uint32; "uint32_t(#{valueStr})"
when :uint64; "uint64_t(#{valueStr})"
when :uintptr; "uintptr_t(#{valueStr})"
else
raise "Not implemented immediate of type: #{type}"
end
end
end
class Address
def clDump
"[#{base.clDump}, #{offset.value}]"
end
def clLValue(type=:intptr)
clValue(type)
end
def clValue(type=:intptr)
case type
when :int8; int8MemRef
when :int16; int16MemRef
when :int32; int32MemRef
when :int64; int64MemRef
when :intptr; intptrMemRef
when :uint8; uint8MemRef
when :uint32; uint32MemRef
when :uint64; uint64MemRef
when :uintptr; uintptrMemRef
when :opcode; opcodeMemRef
when :nativeFunc; nativeFuncMemRef
else
raise "Unexpected Address type: #{type}"
end
end
def pointerExpr
if offset.value == 0
"#{base.clValue(:int8Ptr)}"
elsif offset.value > 0
"#{base.clValue(:int8Ptr)} + #{offset.value}"
else
"#{base.clValue(:int8Ptr)} - #{-offset.value}"
end
end
def int8MemRef
"*CAST<int8_t*>(#{pointerExpr})"
end
def int16MemRef
"*CAST<int16_t*>(#{pointerExpr})"
end
def int32MemRef
"*CAST<int32_t*>(#{pointerExpr})"
end
def int64MemRef
"*CAST<int64_t*>(#{pointerExpr})"
end
def intptrMemRef
"*CAST<intptr_t*>(#{pointerExpr})"
end
def uint8MemRef
"*CAST<uint8_t*>(#{pointerExpr})"
end
def uint16MemRef
"*CAST<uint16_t*>(#{pointerExpr})"
end
def uint32MemRef
"*CAST<uint32_t*>(#{pointerExpr})"
end
def uint64MemRef
"*CAST<uint64_t*>(#{pointerExpr})"
end
def uintptrMemRef
"*CAST<uintptr_t*>(#{pointerExpr})"
end
def nativeFuncMemRef
"*CAST<NativeFunction*>(#{pointerExpr})"
end
def opcodeMemRef
"*CAST<Opcode*>(#{pointerExpr})"
end
def dblMemRef
"*CAST<double*>(#{pointerExpr})"
end
end
class BaseIndex
def clDump
"[#{base.clDump}, #{offset.clDump}, #{index.clDump} << #{scaleShift}]"
end
def clLValue(type=:intptr)
clValue(type)
end
def clValue(type=:intptr)
case type
when :int8; int8MemRef
when :int32; int32MemRef
when :int16; int16MemRef
when :int64; int64MemRef
when :intptr; intptrMemRef
when :uint8; uint8MemRef
when :uint16; uint16MemRef
when :uint32; uint32MemRef
when :uint64; uint64MemRef
when :uintptr; uintptrMemRef
when :opcode; opcodeMemRef
else
raise "Unexpected BaseIndex type: #{type}"
end
end
def pointerExpr
if offset.value == 0
"#{base.clValue(:int8Ptr)} + (#{index.clValue} << #{scaleShift})"
else
"#{base.clValue(:int8Ptr)} + (#{index.clValue} << #{scaleShift}) + #{offset.clValue}"
end
end
def int8MemRef
"*CAST<int8_t*>(#{pointerExpr})"
end
def int16MemRef
"*CAST<int16_t*>(#{pointerExpr})"
end
def int32MemRef
"*CAST<int32_t*>(#{pointerExpr})"
end
def int64MemRef
"*CAST<int64_t*>(#{pointerExpr})"
end
def intptrMemRef
"*CAST<intptr_t*>(#{pointerExpr})"
end
def uint8MemRef
"*CAST<uint8_t*>(#{pointerExpr})"
end
def uint16MemRef
"*CAST<uint16_t*>(#{pointerExpr})"
end
def uint32MemRef
"*CAST<uint32_t*>(#{pointerExpr})"
end
def uint64MemRef
"*CAST<uint64_t*>(#{pointerExpr})"
end
def uintptrMemRef
"*CAST<uintptr_t*>(#{pointerExpr})"
end
def opcodeMemRef
"*CAST<Opcode*>(#{pointerExpr})"
end
def dblMemRef
"*CAST<double*>(#{pointerExpr})"
end
end
class AbsoluteAddress
def clDump
"#{codeOriginString}"
end
def clLValue(type=:intptr)
clValue(type)
end
def clValue
clDump
end
end
class LabelReference
def intptrMemRef
"*CAST<intptr_t*>(&#{cLabel})"
end
def cloopEmitLea(destination, type)
$asm.putc "#{destination.clLValue(:voidPtr)} = CAST<void*>(&#{cLabel});"
if offset != 0
$asm.putc "#{destination.clLValue(:int8Ptr)} = #{destination.clValue(:int8Ptr)} + #{offset};"
end
end
end
#
# Lea support.
#
class Address
def cloopEmitLea(destination, type)
if destination == base
$asm.putc "#{destination.clLValue(:int8Ptr)} = #{destination.clValue(:int8Ptr)} + #{offset.clValue(type)};"
else
$asm.putc "#{destination.clLValue(:int8Ptr)} = #{base.clValue(:int8Ptr)} + #{offset.clValue(type)};"
end
end
end
class BaseIndex
def cloopEmitLea(destination, type)
raise "Malformed BaseIndex, offset should be zero at #{codeOriginString}" unless offset.value == 0
$asm.putc "#{destination.clLValue(:int8Ptr)} = #{base.clValue(:int8Ptr)} + (#{index.clValue} << #{scaleShift});"
end
end
#
# Actual lowering code follows.
#
class Sequence
def getModifiedListC_LOOP
myList = @list
# Verify that we will only see instructions and labels.
myList.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
}
return myList
end
end
def clOperands(operands)
operands.map{|v| v.clDump}.join(", ")
end
def cloopEmitOperation(operands, type, operator)
raise unless type == :intptr || type == :uintptr || type == :int32 || type == :uint32 || \
type == :int64 || type == :uint64 || type == :double || type == :int16
if operands.size == 3
op1 = operands[0]
op2 = operands[1]
dst = operands[2]
else
raise unless operands.size == 2
op1 = operands[1]
op2 = operands[0]
dst = operands[1]
end
raise unless not dst.is_a? Immediate
if dst.is_a? RegisterID and (type == :int32 or type == :uint32)
truncationHeader = "(uint32_t)("
truncationFooter = ")"
elsif dst.is_a? RegisterID and (type == :int16)
truncationHeader = "(uint16_t)("
truncationFooter = ")"
else
truncationHeader = ""
truncationFooter = ""
end
$asm.putc "#{dst.clLValue(type)} = #{truncationHeader}#{op1.clValue(type)} #{operator} #{op2.clValue(type)}#{truncationFooter};"
end
def cloopEmitShiftOperation(operands, type, operator)
raise unless type == :intptr || type == :uintptr || type == :int32 || type == :uint32 || type == :int64 || type == :uint64
if operands.size == 3
op1 = operands[0]
op2 = operands[1]
dst = operands[2]
else
op1 = operands[1]
op2 = operands[0]
dst = operands[1]
end
if dst.is_a? RegisterID and (type == :int32 or type == :uint32)
truncationHeader = "(uint32_t)("
truncationFooter = ")"
else
truncationHeader = ""
truncationFooter = ""
end
shiftMask = "((sizeof(uintptr_t) == 8) ? 0x3f : 0x1f)" if type == :intptr || type == :uintptr
shiftMask = "0x3f" if type == :int64 || type == :uint64
shiftMask = "0x1f" if type == :int32 || type == :uint32
$asm.putc "#{dst.clLValue(type)} = #{truncationHeader}#{operands[1].clValue(type)} #{operator} (#{operands[0].clValue(:intptr)} & #{shiftMask})#{truncationFooter};"
end
def cloopEmitUnaryOperation(operands, type, operator)
raise unless type == :intptr || type == :uintptr || type == :int32 || type == :uint32 || type == :int64 || type == :uint64
raise unless operands.size == 1
raise unless not operands[0].is_a? Immediate
op = operands[0]
dst = operands[0]
if dst.is_a? RegisterID and (type == :int32 or type == :uint32)
truncationHeader = "(uint32_t)("
truncationFooter = ")"
else
truncationHeader = ""
truncationFooter = ""
end
$asm.putc "#{dst.clLValue(type)} = #{truncationHeader}#{operator}#{op.clValue(type)}#{truncationFooter};"
end
def cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, condition)
$asm.putc "if (std::isnan(#{operands[0].clValue(:double)}) || std::isnan(#{operands[1].clValue(:double)})"
$asm.putc " || (#{operands[0].clValue(:double)} #{condition} #{operands[1].clValue(:double)}))"
$asm.putc " goto #{operands[2].cLabel};"
end
def cloopEmitCompareAndSet(operands, type, comparator)
# The result is a boolean. Hence, it doesn't need to be based on the type
# of the arguments being compared.
$asm.putc "#{operands[2].clLValue(type)} = (#{operands[0].clValue(type)} #{comparator} #{operands[1].clValue(type)});"
end
def cloopEmitCompareAndBranch(operands, type, comparator)
$asm.putc "if (#{operands[0].clValue(type)} #{comparator} #{operands[1].clValue(type)})"
$asm.putc " goto #{operands[2].cLabel};"
end
# conditionTest should contain a string that provides a comparator and a RHS
# value e.g. "< 0".
def cloopGenerateConditionExpression(operands, type, conditionTest)
op1 = operands[0].clValue(type)
# The operands must consist of 2 or 3 values.
case operands.size
when 2 # Just test op1 against the conditionTest.
lhs = op1
when 3 # Mask op1 with op2 before testing against the conditionTest.
lhs = "(#{op1} & #{operands[1].clValue(type)})"
else
raise "Expected 2 or 3 operands but got #{operands.size} at #{codeOriginString}"
end
"#{lhs} #{conditionTest}"
end
# conditionTest should contain a string that provides a comparator and a RHS
# value e.g. "< 0".
def cloopEmitTestAndBranchIf(operands, type, conditionTest, branchTarget)
conditionExpr = cloopGenerateConditionExpression(operands, type, conditionTest)
$asm.putc "if (#{conditionExpr})"
$asm.putc " goto #{branchTarget};"
end
def cloopEmitTestSet(operands, type, conditionTest)
# The result is a boolean condition. Hence, the result type is always an
# int. The passed in type is only used for the values being tested in
# the condition test.
conditionExpr = cloopGenerateConditionExpression(operands, type, conditionTest)
$asm.putc "#{operands[-1].clLValue} = (#{conditionExpr});"
end
def cloopEmitOpAndBranch(operands, operator, type, conditionTest)
case type
when :intptr; tempType = "intptr_t"
when :int32; tempType = "int32_t"
when :int64; tempType = "int64_t"
else
raise "Unimplemented type"
end
$asm.putc "{"
$asm.putc " #{tempType} temp = #{operands[1].clValue(type)} #{operator} #{operands[0].clValue(type)};"
$asm.putc " #{operands[1].clLValue(type)} = temp;"
$asm.putc " if (temp #{conditionTest})"
$asm.putc " goto #{operands[2].cLabel};"
$asm.putc "}"
end
def cloopEmitOpAndBranchIfOverflow(operands, operator, type)
case type
when :int32
tempType = "int32_t"
truncationHeader = "(uint32_t)("
truncationFooter = ")"
else
raise "Unimplemented type"
end
$asm.putc "{"
# Emit the overflow test based on the operands and the type:
case operator
when "+"; operation = "add"
when "-"; operation = "sub"
when "*"; operation = "multiply"
else
raise "Unimplemented opeartor"
end
$asm.putc " #{tempType} result;"
$asm.putc " bool success = WTF::ArithmeticOperations<#{tempType}, #{tempType}, #{tempType}>::#{operation}(#{operands[1].clValue(type)}, #{operands[0].clValue(type)}, result);"
$asm.putc " #{operands[1].clLValue(type)} = #{truncationHeader}result#{truncationFooter};"
$asm.putc " if (!success)"
$asm.putc " goto #{operands[2].cLabel};"
$asm.putc "}"
end
# operands: callTarget, currentFrame, currentPC
def cloopEmitCallSlowPath(operands)
$asm.putc "{"
$asm.putc " cloopStack.setCurrentStackPointer(sp.vp());"
$asm.putc " SlowPathReturnType result = #{operands[0].cLabel}(#{operands[1].clDump}, #{operands[2].clDump});"
$asm.putc " decodeResult(result, t0, t1);"
$asm.putc "}"
end
def cloopEmitCallSlowPathVoid(operands)
$asm.putc "cloopStack.setCurrentStackPointer(sp.vp());"
$asm.putc "#{operands[0].cLabel}(#{operands[1].clDump}, #{operands[2].clDump});"
end
def cloopEmitCallSlowPath3(operands)
$asm.putc "{"
$asm.putc " cloopStack.setCurrentStackPointer(sp.vp());"
$asm.putc " SlowPathReturnType result = #{operands[0].cLabel}(#{operands[1].clDump}, #{operands[2].clDump}, #{operands[3].clDump});"
$asm.putc " decodeResult(result, t0, t1);"
$asm.putc "}"
end
def cloopEmitCallSlowPath4(operands)
$asm.putc "{"
$asm.putc " cloopStack.setCurrentStackPointer(sp.vp());"
$asm.putc " SlowPathReturnType result = #{operands[0].cLabel}(#{operands[1].clDump}, #{operands[2].clDump}, #{operands[3].clDump}, #{operands[4].clDump});"
$asm.putc " decodeResult(result, t0, t1);"
$asm.putc "}"
end
class Instruction
def lowerC_LOOP
case opcode
when "addi"
cloopEmitOperation(operands, :int32, "+")
when "addq"
cloopEmitOperation(operands, :int64, "+")
when "addp"
cloopEmitOperation(operands, :intptr, "+")
when "andi"
cloopEmitOperation(operands, :int32, "&")
when "andq"
cloopEmitOperation(operands, :int64, "&")
when "andp"
cloopEmitOperation(operands, :intptr, "&")
when "ori"
cloopEmitOperation(operands, :int32, "|")
when "orq"
cloopEmitOperation(operands, :int64, "|")
when "orp"
cloopEmitOperation(operands, :intptr, "|")
when "orh"
cloopEmitOperation(operands, :int16, "|")
when "xori"
cloopEmitOperation(operands, :int32, "^")
when "xorq"
cloopEmitOperation(operands, :int64, "^")
when "xorp"
cloopEmitOperation(operands, :intptr, "^")
when "lshifti"
cloopEmitShiftOperation(operands, :int32, "<<")
when "lshiftq"
cloopEmitShiftOperation(operands, :int64, "<<")
when "lshiftp"
cloopEmitShiftOperation(operands, :intptr, "<<")
when "rshifti"
cloopEmitShiftOperation(operands, :int32, ">>")
when "rshiftq"
cloopEmitShiftOperation(operands, :int64, ">>")
when "rshiftp"
cloopEmitShiftOperation(operands, :intptr, ">>")
when "urshifti"
cloopEmitShiftOperation(operands, :uint32, ">>")
when "urshiftq"
cloopEmitShiftOperation(operands, :uint64, ">>")
when "urshiftp"
cloopEmitShiftOperation(operands, :uintptr, ">>")
when "muli"
cloopEmitOperation(operands, :int32, "*")
when "mulq"
cloopEmitOperation(operands, :int64, "*")
when "mulp"
cloopEmitOperation(operands, :intptr, "*")
when "subi"
cloopEmitOperation(operands, :int32, "-")
when "subq"
cloopEmitOperation(operands, :int64, "-")
when "subp"
cloopEmitOperation(operands, :intptr, "-")
when "negi"
cloopEmitUnaryOperation(operands, :int32, "-")
when "negq"
cloopEmitUnaryOperation(operands, :int64, "-")
when "negp"
cloopEmitUnaryOperation(operands, :intptr, "-")
when "noti"
cloopEmitUnaryOperation(operands, :int32, "~")
when "loadi"
$asm.putc "#{operands[1].clLValue(:uint32)} = #{operands[0].uint32MemRef};"
# There's no need to call clearHighWord() here because the above will
# automatically take care of 0 extension.
when "loadis"
$asm.putc "#{operands[1].clLValue(:int32)} = #{operands[0].int32MemRef};"
when "loadq"
$asm.putc "#{operands[1].clLValue(:int64)} = #{operands[0].int64MemRef};"
when "loadp"
$asm.putc "#{operands[1].clLValue} = #{operands[0].intptrMemRef};"
when "storei"
$asm.putc "#{operands[1].int32MemRef} = #{operands[0].clValue(:int32)};"
when "storeq"
$asm.putc "#{operands[1].int64MemRef} = #{operands[0].clValue(:int64)};"
when "storep"
$asm.putc "#{operands[1].intptrMemRef} = #{operands[0].clValue(:intptr)};"
when "loadb"
$asm.putc "#{operands[1].clLValue(:intptr)} = #{operands[0].uint8MemRef};"
when "loadbsi"
$asm.putc "#{operands[1].clLValue(:uint32)} = (uint32_t)((int32_t)#{operands[0].int8MemRef});"
when "loadbsq"
$asm.putc "#{operands[1].clLValue(:uint64)} = (int64_t)#{operands[0].int8MemRef};"
when "storeb"
$asm.putc "#{operands[1].uint8MemRef} = #{operands[0].clValue(:int8)};"
when "loadh"
$asm.putc "#{operands[1].clLValue(:intptr)} = #{operands[0].uint16MemRef};"
when "loadhsi"
$asm.putc "#{operands[1].clLValue(:uint32)} = (uint32_t)((int32_t)#{operands[0].int16MemRef});"
when "loadhsq"
$asm.putc "#{operands[1].clLValue(:uint64)} = (int64_t)#{operands[0].int16MemRef};"
when "storeh"
$asm.putc "*#{operands[1].uint16MemRef} = #{operands[0].clValue(:int16)};"
when "loadd"
$asm.putc "#{operands[1].clLValue(:double)} = #{operands[0].dblMemRef};"
when "stored"
$asm.putc "#{operands[1].dblMemRef} = #{operands[0].clValue(:double)};"
when "addd"
cloopEmitOperation(operands, :double, "+")
when "divd"
cloopEmitOperation(operands, :double, "/")
when "subd"
cloopEmitOperation(operands, :double, "-")
when "muld"
cloopEmitOperation(operands, :double, "*")
# Convert an int value to its double equivalent, and store it in a double register.
when "ci2ds"
$asm.putc "#{operands[1].clLValue(:double)} = (double)#{operands[0].clValue(:int32)}; // ci2ds"
when "bdeq"
cloopEmitCompareAndBranch(operands, :double, "==")
when "bdneq"
cloopEmitCompareAndBranch(operands, :double, "!=")
when "bdgt"
cloopEmitCompareAndBranch(operands, :double, ">");
when "bdgteq"
cloopEmitCompareAndBranch(operands, :double, ">=");
when "bdlt"
cloopEmitCompareAndBranch(operands, :double, "<");
when "bdlteq"
cloopEmitCompareAndBranch(operands, :double, "<=");
when "bdequn"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, "==")
when "bdnequn"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, "!=")
when "bdgtun"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, ">")
when "bdgtequn"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, ">=")
when "bdltun"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, "<")
when "bdltequn"
cloopEmitCompareDoubleWithNaNCheckAndBranch(operands, "<=")
when "td2i"
$asm.putc "#{operands[1].clLValue(:intptr)} = (uint32_t)(intptr_t)#{operands[0].clValue(:double)}; // td2i"
when "bcd2i" # operands: srcDbl dstInt slowPath
$asm.putc "{ // bcd2i"
$asm.putc " double d = #{operands[0].clValue(:double)};"
$asm.putc " const int32_t asInt32 = int32_t(d);"
$asm.putc " if (asInt32 != d || (!asInt32 && std::signbit(d))) // true for -0.0"
$asm.putc " goto #{operands[2].cLabel};"
$asm.putc " #{operands[1].clLValue} = (uint32_t)asInt32;"
$asm.putc "}"
when "move"
$asm.putc "#{operands[1].clLValue(:intptr)} = #{operands[0].clValue(:intptr)};"
when "sxi2q"
$asm.putc "#{operands[1].clLValue(:int64)} = #{operands[0].clValue(:int32)};"
when "zxi2q"
$asm.putc "#{operands[1].clLValue(:uint64)} = #{operands[0].clValue(:uint32)};"
when "sxb2i"
$asm.putc "#{operands[1].clLValue(:int32)} = #{operands[0].clValue(:int8)};"
when "sxh2i"
$asm.putc "#{operands[1].clLValue(:int32)} = #{operands[0].clValue(:int16)};"
when "sxb2q"
$asm.putc "#{operands[1].clLValue(:int64)} = #{operands[0].clValue(:int8)};"
when "sxh2q"
$asm.putc "#{operands[1].clLValue(:int64)} = #{operands[0].clValue(:int16)};"
when "nop"
$asm.putc "// nop"
when "bbeq"
cloopEmitCompareAndBranch(operands, :int8, "==")
when "bieq"
cloopEmitCompareAndBranch(operands, :int32, "==")
when "bqeq"
cloopEmitCompareAndBranch(operands, :int64, "==")
when "bpeq"
cloopEmitCompareAndBranch(operands, :intptr, "==")
when "bbneq"
cloopEmitCompareAndBranch(operands, :int8, "!=")
when "bineq"
cloopEmitCompareAndBranch(operands, :int32, "!=")
when "bqneq"
cloopEmitCompareAndBranch(operands, :int64, "!=")
when "bpneq"
cloopEmitCompareAndBranch(operands, :intptr, "!=")
when "bba"
cloopEmitCompareAndBranch(operands, :uint8, ">")
when "bia"
cloopEmitCompareAndBranch(operands, :uint32, ">")
when "bqa"
cloopEmitCompareAndBranch(operands, :uint64, ">")
when "bpa"
cloopEmitCompareAndBranch(operands, :uintptr, ">")
when "bbaeq"
cloopEmitCompareAndBranch(operands, :uint8, ">=")
when "biaeq"
cloopEmitCompareAndBranch(operands, :uint32, ">=")
when "bqaeq"
cloopEmitCompareAndBranch(operands, :uint64, ">=")
when "bpaeq"
cloopEmitCompareAndBranch(operands, :uintptr, ">=")
when "bbb"
cloopEmitCompareAndBranch(operands, :uint8, "<")
when "bib"
cloopEmitCompareAndBranch(operands, :uint32, "<")
when "bqb"
cloopEmitCompareAndBranch(operands, :uint64, "<")
when "bpb"
cloopEmitCompareAndBranch(operands, :uintptr, "<")
when "bbbeq"
cloopEmitCompareAndBranch(operands, :uint8, "<=")
when "bibeq"
cloopEmitCompareAndBranch(operands, :uint32, "<=")
when "bqbeq"
cloopEmitCompareAndBranch(operands, :uint64, "<=")
when "bpbeq"
cloopEmitCompareAndBranch(operands, :uintptr, "<=")
when "bbgt"
cloopEmitCompareAndBranch(operands, :int8, ">")
when "bigt"
cloopEmitCompareAndBranch(operands, :int32, ">")
when "bqgt"
cloopEmitCompareAndBranch(operands, :int64, ">")
when "bpgt"
cloopEmitCompareAndBranch(operands, :intptr, ">")
when "bbgteq"
cloopEmitCompareAndBranch(operands, :int8, ">=")
when "bigteq"
cloopEmitCompareAndBranch(operands, :int32, ">=")
when "bqgteq"
cloopEmitCompareAndBranch(operands, :int64, ">=")
when "bpgteq"
cloopEmitCompareAndBranch(operands, :intptr, ">=")
when "bblt"
cloopEmitCompareAndBranch(operands, :int8, "<")
when "bilt"
cloopEmitCompareAndBranch(operands, :int32, "<")
when "bqlt"
cloopEmitCompareAndBranch(operands, :int64, "<")
when "bplt"
cloopEmitCompareAndBranch(operands, :intptr, "<")
when "bblteq"
cloopEmitCompareAndBranch(operands, :int8, "<=")
when "bilteq"
cloopEmitCompareAndBranch(operands, :int32, "<=")
when "bqlteq"
cloopEmitCompareAndBranch(operands, :int64, "<=")
when "bplteq"
cloopEmitCompareAndBranch(operands, :intptr, "<=")
when "btbz"
cloopEmitTestAndBranchIf(operands, :int8, "== 0", operands[-1].cLabel)
when "btiz"
cloopEmitTestAndBranchIf(operands, :int32, "== 0", operands[-1].cLabel)
when "btqz"
cloopEmitTestAndBranchIf(operands, :int64, "== 0", operands[-1].cLabel)
when "btpz"
cloopEmitTestAndBranchIf(operands, :intptr, "== 0", operands[-1].cLabel)
when "btbnz"
cloopEmitTestAndBranchIf(operands, :int8, "!= 0", operands[-1].cLabel)
when "btinz"
cloopEmitTestAndBranchIf(operands, :int32, "!= 0", operands[-1].cLabel)
when "btqnz"
cloopEmitTestAndBranchIf(operands, :int64, "!= 0", operands[-1].cLabel)
when "btpnz"
cloopEmitTestAndBranchIf(operands, :intptr, "!= 0", operands[-1].cLabel)
when "btbs"
cloopEmitTestAndBranchIf(operands, :int8, "< 0", operands[-1].cLabel)
when "btis"
cloopEmitTestAndBranchIf(operands, :int32, "< 0", operands[-1].cLabel)
when "btqs"
cloopEmitTestAndBranchIf(operands, :int64, "< 0", operands[-1].cLabel)
when "btps"
cloopEmitTestAndBranchIf(operands, :intptr, "< 0", operands[-1].cLabel)
# For jmp, we do not want to assume that we have COMPUTED_GOTO support.
# Fortunately, the only times we should ever encounter indirect jmps is
# when the jmp target is a CLoop opcode (by design).
#
# Hence, we check if the jmp target is a known label reference. If so,
# we can emit a goto directly. If it is not a known target, then we set
# the target in the opcode, and dispatch to it via whatever dispatch
# mechanism is in used.
when "jmp"
if operands[0].is_a? LocalLabelReference or operands[0].is_a? LabelReference
# Handles jumps local or global labels.
$asm.putc "goto #{operands[0].cLabel};"
else
# Handles jumps to some computed target.
# NOTE: must be an opcode handler or a llint glue helper.
$asm.putc "opcode = #{operands[0].clValue(:opcode)};"
$asm.putc "DISPATCH_OPCODE();"
end
when "call"
$asm.putc "CRASH(); // generic call instruction not supported by design!"
when "break"
$asm.putc "CRASH(); // break instruction not implemented."
when "ret"
$asm.putc "opcode = lr.opcode();"
$asm.putc "DISPATCH_OPCODE();"
when "cbeq"
cloopEmitCompareAndSet(operands, :uint8, "==")
when "cieq"
cloopEmitCompareAndSet(operands, :uint32, "==")
when "cqeq"
cloopEmitCompareAndSet(operands, :uint64, "==")
when "cpeq"
cloopEmitCompareAndSet(operands, :uintptr, "==")
when "cbneq"
cloopEmitCompareAndSet(operands, :uint8, "!=")
when "cineq"
cloopEmitCompareAndSet(operands, :uint32, "!=")
when "cqneq"
cloopEmitCompareAndSet(operands, :uint64, "!=")
when "cpneq"
cloopEmitCompareAndSet(operands, :uintptr, "!=")
when "cba"
cloopEmitCompareAndSet(operands, :uint8, ">")
when "cia"
cloopEmitCompareAndSet(operands, :uint32, ">")
when "cqa"
cloopEmitCompareAndSet(operands, :uint64, ">")
when "cpa"
cloopEmitCompareAndSet(operands, :uintptr, ">")
when "cbaeq"
cloopEmitCompareAndSet(operands, :uint8, ">=")
when "ciaeq"
cloopEmitCompareAndSet(operands, :uint32, ">=")
when "cqaeq"
cloopEmitCompareAndSet(operands, :uint64, ">=")
when "cpaeq"
cloopEmitCompareAndSet(operands, :uintptr, ">=")
when "cbb"
cloopEmitCompareAndSet(operands, :uint8, "<")
when "cib"
cloopEmitCompareAndSet(operands, :uint32, "<")
when "cqb"
cloopEmitCompareAndSet(operands, :uint64, "<")
when "cpb"
cloopEmitCompareAndSet(operands, :uintptr, "<")
when "cbbeq"
cloopEmitCompareAndSet(operands, :uint8, "<=")
when "cibeq"
cloopEmitCompareAndSet(operands, :uint32, "<=")
when "cqbeq"
cloopEmitCompareAndSet(operands, :uint64, "<=")
when "cpbeq"
cloopEmitCompareAndSet(operands, :uintptr, "<=")
when "cbgt"
cloopEmitCompareAndSet(operands, :int8, ">")
when "cigt"
cloopEmitCompareAndSet(operands, :int32, ">")
when "cqgt"
cloopEmitCompareAndSet(operands, :int64, ">")
when "cpgt"
cloopEmitCompareAndSet(operands, :intptr, ">")
when "cdgt"
cloopEmitCompareAndSet(operands, :double, ">")
when "cbgteq"
cloopEmitCompareAndSet(operands, :int8, ">=")
when "cigteq"
cloopEmitCompareAndSet(operands, :int32, ">=")
when "cqgteq"
cloopEmitCompareAndSet(operands, :int64, ">=")
when "cpgteq"
cloopEmitCompareAndSet(operands, :intptr, ">=")
when "cdgteq"
cloopEmitCompareAndSet(operands, :double, ">=")
when "cblt"
cloopEmitCompareAndSet(operands, :int8, "<")
when "cilt"
cloopEmitCompareAndSet(operands, :int32, "<")
when "cqlt"
cloopEmitCompareAndSet(operands, :int64, "<")
when "cplt"
cloopEmitCompareAndSet(operands, :intptr, "<")
when "cdlt"
cloopEmitCompareAndSet(operands, :double, "<")
when "cblteq"
cloopEmitCompareAndSet(operands, :int8, "<=")
when "cilteq"
cloopEmitCompareAndSet(operands, :int32, "<=")
when "cqlteq"
cloopEmitCompareAndSet(operands, :int64, "<=")
when "cplteq"
cloopEmitCompareAndSet(operands, :intptr, "<=")
when "cdlteq"
cloopEmitCompareAndSet(operands, :double, "<=")
when "tbs"
cloopEmitTestSet(operands, :int8, "< 0")
when "tis"
cloopEmitTestSet(operands, :int32, "< 0")
when "tqs"
cloopEmitTestSet(operands, :int64, "< 0")
when "tps"
cloopEmitTestSet(operands, :intptr, "< 0")
when "tbz"
cloopEmitTestSet(operands, :int8, "== 0")
when "tiz"
cloopEmitTestSet(operands, :int32, "== 0")
when "tqz"
cloopEmitTestSet(operands, :int64, "== 0")
when "tpz"
cloopEmitTestSet(operands, :intptr, "== 0")
when "tbnz"
cloopEmitTestSet(operands, :int8, "!= 0")
when "tinz"
cloopEmitTestSet(operands, :int32, "!= 0")
when "tqnz"
cloopEmitTestSet(operands, :int64, "!= 0")
when "tpnz"
cloopEmitTestSet(operands, :intptr, "!= 0")
# 64-bit instruction: cdqi (based on X64)
# Sign extends the lower 32 bits of t0, but put the sign extension into
# the lower 32 bits of t1. Leave the upper 32 bits of t0 and t1 unchanged.
when "cdqi"
$asm.putc "{ // cdqi"
$asm.putc " int64_t temp = t0.i32(); // sign extend the low 32bit"
$asm.putc " t0 = (uint32_t)temp; // low word"
$asm.putc " t1 = (uint32_t)(temp >> 32); // high word"
$asm.putc "}"
# 64-bit instruction: idivi op1 (based on X64)
# Divide a 64-bit integer numerator by the specified denominator.
# The numerator is specified in t0 and t1 as follows:
# 1. low 32 bits of the numerator is in the low 32 bits of t0.
# 2. high 32 bits of the numerator is in the low 32 bits of t1.
#
# The resultant quotient is a signed 32-bit int, and is to be stored
# in the lower 32 bits of t0.
# The resultant remainder is a signed 32-bit int, and is to be stored
# in the lower 32 bits of t1.
when "idivi"
# Divide t1,t0 (EDX,EAX) by the specified arg, and store the remainder in t1,
# and quotient in t0:
$asm.putc "{ // idivi"
$asm.putc " int64_t dividend = (int64_t(t1.u32()) << 32) | t0.u32();"
$asm.putc " int64_t divisor = #{operands[0].clValue(:intptr)};"
$asm.putc " t1 = (uint32_t)(dividend % divisor); // remainder"
$asm.putc " t0 = (uint32_t)(dividend / divisor); // quotient"
$asm.putc "}"
# 32-bit instruction: fii2d int32LoOp int32HiOp dblOp (based on ARMv7)
# Decode 2 32-bit ints (low and high) into a 64-bit double.
when "fii2d"
$asm.putc "#{operands[2].clLValue(:double)} = ints2Double(#{operands[0].clValue(:uint32)}, #{operands[1].clValue(:uint32)}); // fii2d"
# 32-bit instruction: f2dii dblOp int32LoOp int32HiOp (based on ARMv7)
# Encode a 64-bit double into 2 32-bit ints (low and high).
when "fd2ii"
$asm.putc "double2Ints(#{operands[0].clValue(:double)}, #{operands[1].clDump}, #{operands[2].clDump}); // fd2ii"
# 64-bit instruction: fq2d int64Op dblOp (based on X64)
# Copy a bit-encoded double in a 64-bit int register to a double register.
when "fq2d"
$asm.putc "#{operands[1].clLValue(:double)} = #{operands[0].clValue(:bitsAsDouble)}; // fq2d"
# 64-bit instruction: fd2q dblOp int64Op (based on X64 instruction set)
# Copy a double as a bit-encoded double into a 64-bit int register.
when "fd2q"
$asm.putc "#{operands[1].clLValue(:int64)} = #{operands[0].clValue(:bitsAsInt64)}; // fd2q"
when "leai"
operands[0].cloopEmitLea(operands[1], :int32)
when "leap"
operands[0].cloopEmitLea(operands[1], :intptr)
when "baddio"
cloopEmitOpAndBranchIfOverflow(operands, "+", :int32)
when "bsubio"
cloopEmitOpAndBranchIfOverflow(operands, "-", :int32)
when "bmulio"
cloopEmitOpAndBranchIfOverflow(operands, "*", :int32)
when "baddis"
cloopEmitOpAndBranch(operands, "+", :int32, "< 0")
when "baddiz"
cloopEmitOpAndBranch(operands, "+", :int32, "== 0")
when "baddinz"
cloopEmitOpAndBranch(operands, "+", :int32, "!= 0")
when "baddqs"
cloopEmitOpAndBranch(operands, "+", :int64, "< 0")
when "baddqz"
cloopEmitOpAndBranch(operands, "+", :int64, "== 0")
when "baddqnz"
cloopEmitOpAndBranch(operands, "+", :int64, "!= 0")
when "baddps"
cloopEmitOpAndBranch(operands, "+", :intptr, "< 0")
when "baddpz"
cloopEmitOpAndBranch(operands, "+", :intptr, "== 0")
when "baddpnz"
cloopEmitOpAndBranch(operands, "+", :intptr, "!= 0")
when "bsubis"
cloopEmitOpAndBranch(operands, "-", :int32, "< 0")
when "bsubiz"
cloopEmitOpAndBranch(operands, "-", :int32, "== 0")
when "bsubinz"
cloopEmitOpAndBranch(operands, "-", :int32, "!= 0")
when "borris"
cloopEmitOpAndBranch(operands, "|", :int32, "< 0")
when "borriz"
cloopEmitOpAndBranch(operands, "|", :int32, "== 0")
when "borrinz"
cloopEmitOpAndBranch(operands, "|", :int32, "!= 0")
when "memfence"
when "push"
operands.each {
| op |
$asm.putc "PUSH(#{op.clDump});"
}
when "pop"
operands.each {
| op |
$asm.putc "POP(#{op.clDump});"
}
# A convenience and compact call to crash because we don't want to use
# the generic llint crash mechanism which relies on the availability
# of the call instruction (which cannot be implemented in a generic
# way, and can be abused if we made it just work for this special case).
# Using a special cloopCrash instruction is cleaner.
when "cloopCrash"
$asm.putc "CRASH();"
# We can't rely on the llint JS call mechanism which actually makes
# use of the call instruction. Instead, we just implement JS calls
# as an opcode dispatch.
when "cloopCallJSFunction"
uid = $asm.newUID
$asm.putc "lr = getOpcode(llint_cloop_did_return_from_js_#{uid});"
$asm.putc "opcode = #{operands[0].clValue(:opcode)};"
$asm.putc "DISPATCH_OPCODE();"
$asm.putsLabel("llint_cloop_did_return_from_js_#{uid}", false)
# We can't do generic function calls with an arbitrary set of args, but
# fortunately we don't have to here. All native function calls always
# have a fixed prototype of 2 args: the passed JSGlobalObject* and CallFrame*.
when "cloopCallNative"
$asm.putc "cloopStack.setCurrentStackPointer(sp.vp());"
$asm.putc "nativeFunc = #{operands[0].clValue(:nativeFunc)};"
$asm.putc "functionReturnValue = JSValue::decode(nativeFunc(jsCast<JSGlobalObject*>(t0.cell()), t1.callFrame()));"
$asm.putc "#if USE(JSVALUE32_64)"
$asm.putc " t1 = functionReturnValue.tag();"
$asm.putc " t0 = functionReturnValue.payload();"
$asm.putc "#else // USE_JSVALUE64)"
$asm.putc " t0 = JSValue::encode(functionReturnValue);"
$asm.putc "#endif // USE_JSVALUE64)"
# We can't do generic function calls with an arbitrary set of args, but
# fortunately we don't have to here. All slow path function calls always
# have a fixed prototype too. See cloopEmitCallSlowPath() for details.
when "cloopCallSlowPath"
cloopEmitCallSlowPath(operands)
when "cloopCallSlowPathVoid"
cloopEmitCallSlowPathVoid(operands)
when "cloopCallSlowPath3"
cloopEmitCallSlowPath3(operands)
when "cloopCallSlowPath4"
cloopEmitCallSlowPath4(operands)
# For debugging only. This is used to insert instrumentation into the
# generated LLIntAssembly.h during llint development only. Do not use
# for production code.
when "cloopDo"
$asm.putc "#{annotation}"
else
lowerDefault
end
end
def lowerC_LOOP_WIN
lowerC_LOOP
end
def recordMetaDataC_LOOP
$asm.codeOrigin codeOriginString if $enableCodeOriginComments
$asm.annotation annotation if $enableInstrAnnotations && (opcode != "cloopDo")
end
end