blob: c422b022b9a8505d91c7f1d3a9d8995ee3047c01 [file] [log] [blame]
#!/usr/bin/env ruby
# Copyright (C) 2015 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 "pathname"
class Opcode
attr_reader :name, :special, :overloads
attr_accessor :kind
def initialize(name, special)
@name = name
@special = special
@kind = :inst
unless special
@overloads = []
end
end
def masmName
name[0].downcase + name[1..-1]
end
end
class Arg
attr_reader :role, :type
def initialize(role, type)
@role = role
@type = type
end
end
class Overload
attr_reader :signature, :forms
def initialize(signature, forms)
@signature = signature
@forms = forms
end
end
class Kind
attr_reader :name
attr_accessor :special
def initialize(name)
@name = name
@special = false
end
def ==(other)
if other.is_a? String
@name == other
else
@name == other.name and @special == other.special
end
end
def Kind.argKinds(kind)
if kind == "Addr"
["Addr", "Stack", "CallArg"]
else
[kind]
end
end
def argKinds
Kind.argKinds(kind)
end
end
class Form
attr_reader :kinds, :altName
def initialize(kinds, altName)
@kinds = kinds
@altName = altName
end
end
class Origin
attr_reader :fileName, :lineNumber
def initialize(fileName, lineNumber)
@fileName = fileName
@lineNumber = lineNumber
end
def to_s
"#{fileName}:#{lineNumber}"
end
end
class Token
attr_reader :origin, :string
def initialize(origin, string)
@origin = origin
@string = string
end
def ==(other)
if other.is_a? Token
@string == other.string
else
@string == other
end
end
def =~(other)
@string =~ other
end
def to_s
"#{@string.inspect} at #{origin}"
end
def parseError(*comment)
if comment.empty?
raise "Parse error: #{to_s}"
else
raise "Parse error: #{to_s}: #{comment[0]}"
end
end
end
def lex(str, fileName)
fileName = Pathname.new(fileName)
result = []
lineNumber = 1
while not str.empty?
case str
when /\A\#([^\n]*)/
# comment, ignore
when /\A\n/
# newline, ignore
lineNumber += 1
when /\A([a-zA-Z0-9_]+)/
result << Token.new(Origin.new(fileName, lineNumber), $&)
when /\A([ \t\r]+)/
# whitespace, ignore
when /\A[,:*\/]/
result << Token.new(Origin.new(fileName, lineNumber), $&)
else
raise "Lexer error at #{Origin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
end
str = $~.post_match
end
result
end
def isUD(token)
token =~ /\A((U)|(D)|(UD))\Z/
end
def isGF(token)
token =~ /\A((G)|(F))\Z/
end
def isKind(token)
token =~ /\A((Tmp)|(Imm)|(Imm64)|(Addr)|(Index)|(RelCond)|(ResCond))\Z/
end
def isKeyword(token)
isUD(token) or isGF(token) or isKind(token) or
token == "special" or token == "as"
end
def isIdentifier(token)
token =~ /\A([a-zA-Z0-9_]+)\Z/ and not isKeyword(token)
end
class Parser
def initialize(data, fileName)
@tokens = lex(data, fileName)
@idx = 0
end
def token
@tokens[@idx]
end
def advance
@idx += 1
end
def parseError(*comment)
if token
token.parseError(*comment)
else
if comment.empty?
raise "Parse error at end of file"
else
raise "Parse error at end of file: #{comment[0]}"
end
end
end
def consume(string)
parseError("Expected #{string}") unless token == string
advance
end
def consumeIdentifier
result = token.string
parseError("Expected identifier") unless isIdentifier(result)
advance
result
end
def consumeRole
result = token.string
parseError("Expected role (U, D, or UD)") unless isUD(result)
advance
result
end
def consumeType
result = token.string
parseError("Expected type (G or F)") unless isGF(result)
advance
result
end
def consumeKind
result = token.string
parseError("Expected kind (Imm, Imm64, Tmp, Addr, Index, RelCond, or ResCond)") unless isKind(result)
advance
result
end
def parse
result = {}
loop {
break if @idx >= @tokens.length
if token == "special"
consume("special")
opcodeName = consumeIdentifier
parseError("Cannot overload a special opcode") if result[opcodeName]
result[opcodeName] = Opcode.new(opcodeName, true)
else
opcodeName = consumeIdentifier
if result[opcodeName]
opcode = result[opcodeName]
parseError("Cannot overload a special opcode") if opcode.special
else
opcode = Opcode.new(opcodeName, false)
result[opcodeName] = opcode
end
signature = []
forms = []
if isUD(token)
loop {
role = consumeRole
consume(":")
type = consumeType
signature << Arg.new(role, type)
break unless token == ","
consume(",")
}
end
if token == "/"
consume("/")
case token.string
when "branch"
opcode.kind = :branch
when "terminal"
opcode.kind = :terminal
else
parseError("Bad / directive")
end
advance
end
if isKind(token)
loop {
kinds = []
altName = nil
loop {
kinds << Kind.new(consumeKind)
if token == "*"
parseError("Can only apply * to Tmp") unless kinds[-1].name == "Tmp"
kinds[-1].special = true
consume("*")
end
break unless token == ","
consume(",")
}
if token == "as"
consume("as")
altName = consumeIdentifier
end
parseError("Form has wrong number of arguments for overload") unless kinds.length == signature.length
kinds.each_with_index {
| kind, index |
if kind.name == "Imm" or kind.name == "Imm64"
if signature[index].role != "U"
parseError("Form has an immediate for a non-use argument")
end
if signature[index].type != "G"
parseError("Form has an immediate for a non-general-purpose argument")
end
end
}
forms << Form.new(kinds, altName)
break unless isKind(token)
}
end
if signature.length == 0
raise unless forms.length == 0
forms << Form.new([], nil)
end
opcode.overloads << Overload.new(signature, forms)
end
}
result
end
end
$fileName = ARGV[0]
parser = Parser.new(IO::read($fileName), $fileName)
$opcodes = parser.parse
$stderr.puts "Generating code for #{$fileName}."
def writeH(filename)
File.open("Air#{filename}.h", "w") {
| outp |
outp.puts "// Generated by opcode_generator.rb from #{$fileName} -- do not edit!"
outp.puts "#ifndef Air#{filename}_h"
outp.puts "#define Air#{filename}_h"
yield outp
outp.puts "#endif // Air#{filename}_h"
}
end
writeH("Opcode") {
| outp |
outp.puts "namespace JSC { namespace B3 { namespace Air {"
outp.puts "enum Opcode : int16_t {"
$opcodes.keys.sort.each {
| opcode |
outp.puts " #{opcode},"
}
outp.puts "};"
outp.puts "static const unsigned numOpcodes = #{$opcodes.keys.size};"
outp.puts "} } } // namespace JSC::B3::Air"
outp.puts "namespace WTF {"
outp.puts "class PrintStream;"
outp.puts "void printInternal(PrintStream&, JSC::B3::Air::Opcode);"
outp.puts "} // namespace WTF"
}
# From here on, we don't try to emit properly indented code, since we're using a recursive pattern
# matcher.
def matchForms(outp, speed, forms, columnIndex, columnGetter, filter, callback)
return if forms.length == 0
if filter[forms]
return
end
if columnIndex >= forms[0].kinds.length
raise unless forms.length == 1
callback[forms[0]]
return
end
groups = {}
forms.each {
| form |
kind = form.kinds[columnIndex].name
if groups[kind]
groups[kind] << form
else
groups[kind] = [form]
end
}
if speed == :fast and groups.length == 1
matchForms(outp, speed, forms, columnIndex + 1, columnGetter, filter, callback)
return
end
outp.puts "switch (#{columnGetter[columnIndex]}) {"
groups.each_pair {
| key, value |
outp.puts "#if USE(JSVALUE64)" if key == "Imm64"
Kind.argKinds(key).each {
| argKind |
outp.puts "case Arg::#{argKind}:"
}
matchForms(outp, speed, value, columnIndex + 1, columnGetter, filter, callback)
outp.puts "break;"
outp.puts "#endif // USE(JSVALUE64)" if key == "Imm64"
}
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
end
def matchInstOverload(outp, speed, inst)
outp.puts "switch (#{inst}->opcode) {"
$opcodes.values.each {
| opcode |
outp.puts "case #{opcode.name}:"
if opcode.special
yield opcode, nil
else
needOverloadSwitch = ((opcode.overloads.size != 1) or speed == :safe)
outp.puts "switch (#{inst}->args.size()) {" if needOverloadSwitch
opcode.overloads.each {
| overload |
outp.puts "case #{overload.signature.length}:" if needOverloadSwitch
yield opcode, overload
outp.puts "break;" if needOverloadSwitch
}
if needOverloadSwitch
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
end
end
outp.puts "break;"
}
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
end
def matchInstOverloadForm(outp, speed, inst)
matchInstOverload(outp, speed, inst) {
| opcode, overload |
if opcode.special
yield opcode, nil, nil
else
columnGetter = proc {
| columnIndex |
"#{inst}->args[#{columnIndex}].kind()"
}
filter = proc { false }
callback = proc {
| form |
yield opcode, overload, form
}
matchForms(outp, speed, overload.forms, 0, columnGetter, filter, callback)
end
}
end
writeH("OpcodeUtils") {
| outp |
outp.puts "#include \"AirInst.h\""
outp.puts "#include \"AirSpecial.h\""
outp.puts "namespace JSC { namespace B3 { namespace Air {"
outp.puts "inline bool opgenHiddenTruth() { return true; }"
outp.puts "template<typename T>"
outp.puts "inline T* opgenHiddenPtrIdentity(T* pointer) { return pointer; }"
outp.puts "#define OPGEN_RETURN(value) do {\\"
outp.puts " if (opgenHiddenTruth())\\"
outp.puts " return value;\\"
outp.puts "} while (false)"
outp.puts "template<typename Functor>"
outp.puts "void Inst::forEachArg(const Functor& functor)"
outp.puts "{"
matchInstOverload(outp, :fast, "this") {
| opcode, overload |
if opcode.special
outp.puts "functor(args[0], Arg::Use, Arg::GP); // This is basically bogus, but it works f analyses model Special as an immediate."
outp.puts "args[0].special()->forEachArg(*this, scopedLambda<EachArgCallback>(functor));"
else
overload.signature.each_with_index {
| arg, index |
role = nil
case arg.role
when "U"
role = "Use"
when "D"
role = "Def"
when "UD"
role = "UseDef"
else
raise
end
outp.puts "functor(args[#{index}], Arg::#{role}, Arg::#{arg.type}P);"
}
end
}
outp.puts "}"
outp.puts "template<typename... Arguments>"
outp.puts "ALWAYS_INLINE bool isValidForm(Opcode opcode, Arguments... arguments)"
outp.puts "{"
outp.puts "Arg::Kind kinds[sizeof...(Arguments)] = { arguments... };"
outp.puts "switch (opcode) {"
$opcodes.values.each {
| opcode |
outp.puts "case #{opcode.name}:"
outp.puts "switch (sizeof...(Arguments)) {"
unless opcode.special
opcode.overloads.each {
| overload |
outp.puts "case #{overload.signature.length}:"
columnGetter = proc { | columnIndex | "opgenHiddenPtrIdentity(kinds)[#{columnIndex}]" }
filter = proc { false }
callback = proc {
| form |
special = (not form.kinds.detect { | kind | kind.special })
outp.puts "OPGEN_RETURN(#{special});"
}
matchForms(outp, :safe, overload.forms, 0, columnGetter, filter, callback)
outp.puts "break;"
}
end
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
outp.puts "break;"
}
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
outp.puts "return false; "
outp.puts "}"
outp.puts "inline bool isTerminal(Opcode opcode)"
outp.puts "{"
outp.puts "switch (opcode) {"
$opcodes.values.each {
| opcode |
if opcode.kind == :terminal or opcode.kind == :branch
outp.puts "case #{opcode.name}:"
end
}
outp.puts "return true;"
outp.puts "default:"
outp.puts "return false;"
outp.puts "}"
outp.puts "}"
outp.puts "} } } // namespace JSC::B3::Air"
}
writeH("OpcodeGenerated") {
| outp |
outp.puts "#include \"AirInstInlines.h\""
outp.puts "#include \"wtf/PrintStream.h\""
outp.puts "namespace WTF {"
outp.puts "using namespace JSC::B3::Air;"
outp.puts "void printInternal(PrintStream& out, Opcode opcode)"
outp.puts "{"
outp.puts " switch (opcode) {"
$opcodes.keys.each {
| opcode |
outp.puts " case #{opcode}:"
outp.puts " out.print(\"#{opcode}\");"
outp.puts " return;"
}
outp.puts " }"
outp.puts " RELEASE_ASSERT_NOT_REACHED();"
outp.puts "}"
outp.puts "} // namespace WTF"
outp.puts "namespace JSC { namespace B3 { namespace Air {"
outp.puts "bool Inst::isValidForm()"
outp.puts "{"
matchInstOverloadForm(outp, :safe, "this") {
| opcode, overload, form |
if opcode.special
outp.puts "if (args.size() < 1)"
outp.puts "return false;"
outp.puts "if (!args[0].isSpecial())"
outp.puts "return false;"
outp.puts "OPGEN_RETURN(args[0].special()->isValid(*this));"
else
needsMoreValidation = false
overload.signature.length.times {
| index |
role = overload.signature[index].role
type = overload.signature[index].type
kind = form.kinds[index]
needsMoreValidation |= kind.special
# We already know that the form matches. We don't have to validate the role, since
# kind implies role. So, the only thing left to validate is the type. And we only have
# to validate the type if we have a Tmp.
if kind.name == "Tmp"
outp.puts "if (!args[#{index}].tmp().is#{type}P())"
outp.puts "OPGEN_RETURN(false);"
end
}
if needsMoreValidation
outp.puts "if (!is#{opcode.name}Valid(*this))"
outp.puts "OPGEN_RETURN(false);"
end
outp.puts "OPGEN_RETURN(true);"
end
}
outp.puts "return false;"
outp.puts "}"
outp.puts "bool Inst::admitsStack(unsigned argIndex)"
outp.puts "{"
outp.puts "switch (opcode) {"
$opcodes.values.each {
| opcode |
outp.puts "case #{opcode.name}:"
if opcode.special
outp.puts "if (!argIndex)"
outp.puts "return false;"
outp.puts "OPGEN_RETURN(args[0].special()->admitsStack(*this, argIndex));"
else
# Switch on the argIndex.
outp.puts "switch (argIndex) {"
numArgs = opcode.overloads.map {
| overload |
overload.signature.length
}.max
numArgs.times {
| argIndex |
outp.puts "case #{argIndex}:"
# Check if all of the forms of all of the overloads either do, or don't, admit an address
# at this index. We expect this to be a very common case.
numYes = 0
numNo = 0
opcode.overloads.each {
| overload |
overload.forms.each {
| form |
if form.kinds[argIndex] == "Addr"
numYes += 1
else
numNo += 1
end
}
}
# Note that we deliberately test numYes first because if we end up with no forms, we want
# to say that Address is inadmissible.
if numYes == 0
outp.puts "OPGEN_RETURN(false);"
elsif numNo == 0
outp.puts "OPGEN_RETURN(true);"
else
# Now do the full test.
needOverloadSwitch = (opcode.overloads.size != 1)
outp.puts "switch (args.size()) {" if needOverloadSwitch
opcode.overloads.each {
| overload |
# Again, check if all of them do what we want.
numYes = 0
numNo = 0
overload.forms.each {
| form |
if form.kinds[argIndex] == "Addr"
numYes += 1
else
numNo += 1
end
}
if numYes == 0
# Don't emit anything, just drop to default.
elsif numNo == 0
outp.puts "case #{overload.signature.length}:" if needOverloadSwitch
outp.puts "OPGEN_RETURN(true);"
outp.puts "break;" if needOverloadSwitch
else
outp.puts "case #{overload.signature.length}:" if needOverloadSwitch
# This is how we test the hypothesis that changing this argument to an
# address yields a valid form.
columnGetter = proc {
| columnIndex |
if columnIndex == argIndex
"Arg::Addr"
else
"args[#{columnIndex}].kind()"
end
}
filter = proc {
| forms |
numYes = 0
forms.each {
| form |
if form.kinds[argIndex] == "Addr"
numYes += 1
end
}
if numYes == 0
# Drop down, emit no code, since we cannot match.
true
else
# Keep going.
false
end
}
callback = proc {
outp.puts "OPGEN_RETURN(true);"
}
matchForms(outp, :safe, overload.forms, 0, columnGetter, filter, callback)
outp.puts "break;" if needOverloadSwitch
end
}
if needOverloadSwitch
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
end
end
outp.puts "break;"
}
outp.puts "default:"
outp.puts "break;"
outp.puts "}"
end
outp.puts "break;"
}
outp.puts "default:";
outp.puts "break;"
outp.puts "}"
outp.puts "return false;"
outp.puts "}"
outp.puts "CCallHelpers::Jump Inst::generate(CCallHelpers& jit, GenerationContext& context)"
outp.puts "{"
outp.puts "UNUSED_PARAM(jit);"
outp.puts "UNUSED_PARAM(context);"
outp.puts "CCallHelpers::Jump result;"
matchInstOverloadForm(outp, :fast, "this") {
| opcode, overload, form |
if opcode.special
outp.puts "OPGEN_RETURN(args[0].special()->generate(*this, jit, context));"
else
if form.altName
methodName = form.altName
else
methodName = opcode.masmName
end
if opcode.kind == :branch
outp.print "result = "
end
outp.print "jit.#{methodName}("
form.kinds.each_with_index {
| kind, index |
if index != 0
outp.print ", "
end
case kind.name
when "Tmp"
if overload.signature[index].type == "G"
outp.print "args[#{index}].gpr()"
else
outp.print "args[#{index}].fpr()"
end
when "Imm"
outp.print "args[#{index}].asTrustedImm32()"
when "Imm64"
outp.print "args[#{index}].asTrustedImm64()"
when "Addr"
outp.print "args[#{index}].asAddress()"
when "Index"
outp.print "args[#{index}].asBaseIndex()"
when "RelCond"
outp.print "args[#{index}].asRelationalCondition()"
when "ResCond"
outp.print "args[#{index}].asResultCondition()"
end
}
outp.puts ");"
outp.puts "OPGEN_RETURN(result);"
end
}
outp.puts "RELEASE_ASSERT_NOT_REACHED();"
outp.puts "return result;"
outp.puts "}"
outp.puts "} } } // namespace JSC::B3::Air"
}