diff options
Diffstat (limited to 'Source/JavaScriptCore/b3/air/opcode_generator.rb')
-rw-r--r-- | Source/JavaScriptCore/b3/air/opcode_generator.rb | 1228 |
1 files changed, 1228 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/b3/air/opcode_generator.rb b/Source/JavaScriptCore/b3/air/opcode_generator.rb new file mode 100644 index 000000000..d14240515 --- /dev/null +++ b/Source/JavaScriptCore/b3/air/opcode_generator.rb @@ -0,0 +1,1228 @@ +#!/usr/bin/env ruby + +# Copyright (C) 2015-2016 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, :custom, :overloads + attr_reader :attributes + + def initialize(name, custom) + @name = name + @custom = custom + @attributes = {} + unless custom + @overloads = [] + end + end + + def masmName + name[0].downcase + name[1..-1] + end +end + +class Arg + attr_reader :role, :type, :width + + def initialize(role, type, width) + @role = role + @type = type + @width = width + end + + def widthCode + if width == "Ptr" + "Arg::pointerWidth()" + else + "Arg::Width#{width}" + end + 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 :custom + + def initialize(name) + @name = name + @custom = false + end + + def ==(other) + if other.is_a? String + @name == other + else + @name == other.name and @custom == other.custom + 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, :archs + + def initialize(kinds, altName, archs) + @kinds = kinds + @altName = altName + @archs = archs + 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 isRole(token) + token =~ /\A((U)|(D)|(UD)|(ZD)|(UZD)|(UA)|(S))\Z/ +end + +def isGF(token) + token =~ /\A((G)|(F))\Z/ +end + +def isKind(token) + token =~ /\A((Tmp)|(Imm)|(BigImm)|(BitImm)|(BitImm64)|(Addr)|(Index)|(RelCond)|(ResCond)|(DoubleCond))\Z/ +end + +def isArch(token) + token =~ /\A((x86)|(x86_32)|(x86_64)|(arm)|(armv7)|(arm64)|(32)|(64))\Z/ +end + +def isWidth(token) + token =~ /\A((8)|(16)|(32)|(64)|(Ptr))\Z/ +end + +def isKeyword(token) + isRole(token) or isGF(token) or isKind(token) or isArch(token) or isWidth(token) or + token == "custom" 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, UD, ZD, UZD, UA, or S)") unless isRole(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, BigImm, BitImm, BitImm64, Tmp, Addr, Index, RelCond, ResCond, or DoubleCond)") unless isKind(result) + advance + result + end + + def consumeWidth + result = token.string + parseError("Expected width (8, 16, 32, or 64)") unless isWidth(result) + advance + result + end + + def parseArchs + return nil unless isArch(token) + + result = [] + while isArch(token) + case token.string + when "x86" + result << "X86" + result << "X86_64" + when "x86_32" + result << "X86" + when "x86_64" + result << "X86_64" + when "arm" + result << "ARMv7" + result << "ARM64" + when "armv7" + result << "ARMv7" + when "arm64" + result << "ARM64" + when "32" + result << "X86" + result << "ARMv7" + when "64" + result << "X86_64" + result << "ARM64" + else + raise token.string + end + advance + end + + consume(":") + @lastArchs = result + end + + def consumeArchs + result = @lastArchs + @lastArchs = nil + result + end + + def parseAndConsumeArchs + parseArchs + consumeArchs + end + + def intersectArchs(left, right) + return left unless right + return right unless left + + left.select { + | value | + right.find { + | otherValue | + value == otherValue + } + } + end + + def parse + result = {} + + loop { + break if @idx >= @tokens.length + + if token == "custom" + consume("custom") + opcodeName = consumeIdentifier + + parseError("Cannot overload a custom opcode") if result[opcodeName] + + result[opcodeName] = Opcode.new(opcodeName, true) + else + opcodeArchs = parseAndConsumeArchs + + opcodeName = consumeIdentifier + + if result[opcodeName] + opcode = result[opcodeName] + parseError("Cannot overload a custom opcode") if opcode.custom + else + opcode = Opcode.new(opcodeName, false) + result[opcodeName] = opcode + end + + signature = [] + forms = [] + + if isRole(token) + loop { + role = consumeRole + consume(":") + type = consumeType + consume(":") + width = consumeWidth + + signature << Arg.new(role, type, width) + + break unless token == "," + consume(",") + } + end + + while token == "/" + consume("/") + case token.string + when "branch" + opcode.attributes[:branch] = true + opcode.attributes[:terminal] = true + when "terminal" + opcode.attributes[:terminal] = true + when "effects" + opcode.attributes[:effects] = true + when "return" + opcode.attributes[:return] = true + opcode.attributes[:terminal] = true + else + parseError("Bad / directive") + end + advance + end + + parseArchs + if isKind(token) + loop { + kinds = [] + altName = nil + formArchs = consumeArchs + loop { + kinds << Kind.new(consumeKind) + + if token == "*" + parseError("Can only apply * to Tmp") unless kinds[-1].name == "Tmp" + kinds[-1].custom = 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 == "BigImm" or kind.name == "BitImm" or kind.name == "BitImm64" + 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, intersectArchs(opcodeArchs, formArchs)) + + parseArchs + break unless isKind(token) + } + end + + if signature.length == 0 + raise unless forms.length == 0 + forms << Form.new([], nil, opcodeArchs) + end + + opcode.overloads << Overload.new(signature, forms) + end + } + + result + end +end + +$fileName = ARGV[0] + +parser = Parser.new(IO::read($fileName), $fileName) +$opcodes = parser.parse + +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 "JS_EXPORT_PRIVATE 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 "Did not reduce to one form: #{forms.inspect}" unless forms.length == 1 + callback[forms[0]] + outp.puts "break;" + 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 == "BigImm" or key == "BitImm64" + 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 == "BigImm" or key == "BitImm64" + } + outp.puts "default:" + outp.puts "break;" + outp.puts "}" +end + +def matchInstOverload(outp, speed, inst) + outp.puts "switch (#{inst}->kind.opcode) {" + $opcodes.values.each { + | opcode | + outp.puts "case #{opcode.name}:" + if opcode.custom + 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.custom + 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 + +def beginArchs(outp, archs) + return unless archs + if archs.empty? + outp.puts "#if 0" + return + end + outp.puts("#if " + archs.map { + | arch | + "CPU(#{arch})" + }.join(" || ")) +end + +def endArchs(outp, archs) + return unless archs + outp.puts "#endif" +end + +writeH("OpcodeUtils") { + | outp | + outp.puts "#include \"AirCustom.h\"" + outp.puts "#include \"AirInst.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.custom + outp.puts "#{opcode.name}Custom::forEachArg(*this, functor);" + else + overload.signature.each_with_index { + | arg, index | + + role = nil + case arg.role + when "U" + role = "Use" + when "D" + role = "Def" + when "ZD" + role = "ZDef" + when "UD" + role = "UseDef" + when "UZD" + role = "UseZDef" + when "UA" + role = "UseAddr" + when "S" + role = "Scratch" + else + raise + end + + outp.puts "functor(args[#{index}], Arg::#{role}, Arg::#{arg.type}P, #{arg.widthCode});" + } + 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}:" + if opcode.custom + outp.puts "OPGEN_RETURN(#{opcode.name}Custom::isValidFormStatic(arguments...));" + else + outp.puts "switch (sizeof...(Arguments)) {" + opcode.overloads.each { + | overload | + outp.puts "case #{overload.signature.length}:" + columnGetter = proc { | columnIndex | "opgenHiddenPtrIdentity(kinds)[#{columnIndex}]" } + filter = proc { false } + callback = proc { + | form | + # This conservatively says that Stack is not a valid form for UseAddr, + # because it's only valid if it's not a spill slot. This is consistent with + # isValidForm() being conservative and it also happens to be practical since + # we don't really use isValidForm for deciding when Stack is safe. + overload.signature.length.times { + | index | + if overload.signature[index].role == "UA" + outp.puts "if (opgenHiddenPtrIdentity(kinds)[#{index}] == Arg::Stack)" + outp.puts " return false;" + end + } + + notCustom = (not form.kinds.detect { | kind | kind.custom }) + if notCustom + beginArchs(outp, form.archs) + outp.puts "OPGEN_RETURN(true);" + endArchs(outp, form.archs) + end + } + matchForms(outp, :safe, overload.forms, 0, columnGetter, filter, callback) + 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 "inline bool isDefinitelyTerminal(Opcode opcode)" + outp.puts "{" + outp.puts "switch (opcode) {" + didFindTerminals = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:terminal] + outp.puts "case #{opcode.name}:" + didFindTerminals = true + end + } + if didFindTerminals + outp.puts "return true;" + end + outp.puts "default:" + outp.puts "return false;" + outp.puts "}" + outp.puts "}" + + outp.puts "inline bool isReturn(Opcode opcode)" + outp.puts "{" + outp.puts "switch (opcode) {" + didFindReturns = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:return] + outp.puts "case #{opcode.name}:" + didFindReturns = true + end + } + if didFindReturns + outp.puts "return true;" + end + 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.custom + outp.puts "OPGEN_RETURN(#{opcode.name}Custom::isValidForm(*this));" + else + beginArchs(outp, form.archs) + needsMoreValidation = false + overload.signature.length.times { + | index | + arg = overload.signature[index] + kind = form.kinds[index] + needsMoreValidation |= kind.custom + + # Some kinds of Args reqire additional validation. + case kind.name + when "Tmp" + outp.puts "if (!args[#{index}].tmp().is#{arg.type}P())" + outp.puts "OPGEN_RETURN(false);" + when "Imm" + outp.puts "if (!Arg::isValidImmForm(args[#{index}].value()))" + outp.puts "OPGEN_RETURN(false);" + when "BitImm" + outp.puts "if (!Arg::isValidBitImmForm(args[#{index}].value()))" + outp.puts "OPGEN_RETURN(false);" + when "BitImm64" + outp.puts "if (!Arg::isValidBitImm64Form(args[#{index}].value()))" + outp.puts "OPGEN_RETURN(false);" + when "Addr" + if arg.role == "UA" + outp.puts "if (args[#{index}].isStack() && args[#{index}].stackSlot()->isSpill())" + outp.puts "OPGEN_RETURN(false);" + end + + outp.puts "if (!Arg::isValidAddrForm(args[#{index}].offset()))" + outp.puts "OPGEN_RETURN(false);" + when "Index" + outp.puts "if (!Arg::isValidIndexForm(args[#{index}].scale(), args[#{index}].offset(), #{arg.widthCode}))" + outp.puts "OPGEN_RETURN(false);" + when "BigImm" + when "RelCond" + when "ResCond" + when "DoubleCond" + else + raise "Unexpected kind: #{kind.name}" + end + } + if needsMoreValidation + outp.puts "if (!is#{opcode.name}Valid(*this))" + outp.puts "OPGEN_RETURN(false);" + end + outp.puts "OPGEN_RETURN(true);" + endArchs(outp, form.archs) + end + } + outp.puts "return false;" + outp.puts "}" + + outp.puts "bool Inst::admitsStack(unsigned argIndex)" + outp.puts "{" + outp.puts "switch (kind.opcode) {" + $opcodes.values.each { + | opcode | + outp.puts "case #{opcode.name}:" + + if opcode.custom + outp.puts "OPGEN_RETURN(#{opcode.name}Custom::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 | + useAddr = (overload.signature[argIndex] and + overload.signature[argIndex].role == "UA") + overload.forms.each { + | form | + if form.kinds[argIndex] == "Addr" and not useAddr + 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 | + + useAddr = (overload.signature[argIndex] and + overload.signature[argIndex].role == "UA") + + # Again, check if all of them do what we want. + numYes = 0 + numNo = 0 + overload.forms.each { + | form | + if form.kinds[argIndex] == "Addr" and not useAddr + 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 { + | form | + beginArchs(outp, form.archs) + outp.puts "OPGEN_RETURN(true);" + endArchs(outp, form.archs) + } + 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 "bool Inst::isTerminal()" + outp.puts "{" + outp.puts "switch (kind.opcode) {" + foundTrue = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:terminal] + outp.puts "case #{opcode.name}:" + foundTrue = true + end + } + if foundTrue + outp.puts "return true;" + end + $opcodes.values.each { + | opcode | + if opcode.custom + outp.puts "case #{opcode.name}:" + outp.puts "return #{opcode.name}Custom::isTerminal(*this);" + end + } + outp.puts "default:" + outp.puts "return false;" + outp.puts "}" + outp.puts "}" + + outp.puts "bool Inst::hasNonArgNonControlEffects()" + outp.puts "{" + outp.puts "if (kind.traps)" + outp.puts "return true;" + outp.puts "switch (kind.opcode) {" + foundTrue = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:effects] + outp.puts "case #{opcode.name}:" + foundTrue = true + end + } + if foundTrue + outp.puts "return true;" + end + $opcodes.values.each { + | opcode | + if opcode.custom + outp.puts "case #{opcode.name}:" + outp.puts "return #{opcode.name}Custom::hasNonArgNonControlEffects(*this);" + end + } + outp.puts "default:" + outp.puts "return false;" + outp.puts "}" + outp.puts "}" + + outp.puts "bool Inst::hasNonArgEffects()" + outp.puts "{" + outp.puts "if (kind.traps)" + outp.puts "return true;" + outp.puts "switch (kind.opcode) {" + foundTrue = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:terminal] or opcode.attributes[:effects] + outp.puts "case #{opcode.name}:" + foundTrue = true + end + } + if foundTrue + outp.puts "return true;" + end + $opcodes.values.each { + | opcode | + if opcode.custom + outp.puts "case #{opcode.name}:" + outp.puts "return #{opcode.name}Custom::hasNonArgEffects(*this);" + end + } + outp.puts "default:" + outp.puts "return false;" + outp.puts "}" + 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.custom + outp.puts "OPGEN_RETURN(#{opcode.name}Custom::generate(*this, jit, context));" + else + beginArchs(outp, form.archs) + if form.altName + methodName = form.altName + else + methodName = opcode.masmName + end + if opcode.attributes[: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", "BitImm" + outp.print "args[#{index}].asTrustedImm32()" + when "BigImm", "BitImm64" + 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()" + when "DoubleCond" + outp.print "args[#{index}].asDoubleCondition()" + end + } + + outp.puts ");" + outp.puts "OPGEN_RETURN(result);" + endArchs(outp, form.archs) + end + } + outp.puts "RELEASE_ASSERT_NOT_REACHED();" + outp.puts "return result;" + outp.puts "}" + + outp.puts "} } } // namespace JSC::B3::Air" +} + +# This is a hack for JSAir. It's a joke. +File.open("JSAir_opcode.js", "w") { + | outp | + outp.puts "\"use strict\";" + outp.puts "// Generated by opcode_generator.rb from #{$fileName} -- do not edit!" + + $opcodes.values.each { + | opcode | + outp.puts "const #{opcode.name} = Symbol(#{opcode.name.inspect});" + } + + outp.puts "function Inst_forEachArg(inst, func)" + outp.puts "{" + outp.puts "let replacement;" + outp.puts "switch (inst.opcode) {" + $opcodes.values.each { + | opcode | + outp.puts "case #{opcode.name}:" + if opcode.custom + outp.puts "#{opcode.name}Custom.forEachArg(inst, func);" + else + needOverloadSwitch = opcode.overloads.size != 1 + outp.puts "switch (inst.args.length) {" if needOverloadSwitch + opcode.overloads.each { + | overload | + outp.puts "case #{overload.signature.length}:" if needOverloadSwitch + overload.signature.each_with_index { + | arg, index | + role = nil + case arg.role + when "U" + role = "Use" + when "D" + role = "Def" + when "ZD" + role = "ZDef" + when "UD" + role = "UseDef" + when "UZD" + role = "UseZDef" + when "UA" + role = "UseAddr" + when "S" + role = "Scratch" + else + raise + end + + outp.puts "inst.visitArg(#{index}, func, Arg.#{role}, #{arg.type}P, #{arg.width});" + } + outp.puts "break;" + } + if needOverloadSwitch + outp.puts "default:" + outp.puts "throw new Error(\"Bad overload\");" + outp.puts "break;" + outp.puts "}" + end + end + outp.puts "break;" + } + outp.puts "default:" + outp.puts "throw \"Bad opcode\";" + outp.puts "}" + outp.puts "}" + + outp.puts "function Inst_hasNonArgEffects(inst)" + outp.puts "{" + outp.puts "switch (inst.opcode) {" + foundTrue = false + $opcodes.values.each { + | opcode | + if opcode.attributes[:terminal] or opcode.attributes[:effects] + outp.puts "case #{opcode.name}:" + foundTrue = true + end + } + if foundTrue + outp.puts "return true;" + end + $opcodes.values.each { + | opcode | + if opcode.custom + outp.puts "case #{opcode.name}:" + outp.puts "return #{opcode.name}Custom.hasNonArgNonControlEffects(inst);" + end + } + outp.puts "default:" + outp.puts "return false;" + outp.puts "}" + outp.puts "}" + + outp.puts "function opcodeCode(opcode)" + outp.puts "{" + outp.puts "switch (opcode) {" + $opcodes.keys.sort.each_with_index { + | opcode, index | + outp.puts "case #{opcode}:" + outp.puts "return #{index}" + } + outp.puts "default:" + outp.puts "throw new Error(\"bad opcode\");" + outp.puts "}" + outp.puts "}" +} + |