# Copyright (C) 2018-2019 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_relative 'Argument' require_relative 'Fits' require_relative 'Metadata' class Opcode attr_reader :id attr_reader :args attr_reader :metadata module Size Narrow = "OpcodeSize::Narrow" Wide = "OpcodeSize::Wide" end @@id = 0 def self.id tid = @@id @@id = @@id + 1 tid end def initialize(section, name, args, metadata, metadata_initializers) @section = section @name = name @metadata = Metadata.new metadata, metadata_initializers @args = args.map.with_index { |(arg_name, type), index| Argument.new arg_name, type, index + 1 } unless args.nil? end def create_id! @id = self.class.id end def print_args(&block) return if @args.nil? @args.map(&block).join "\n" end def print_members(prefix, &block) return if @args.nil? @args.map(&block).map { |arg| "#{prefix}#{arg}" }.join "\n" end def capitalized_name name.split('_').map(&:capitalize).join end def typed_args return if @args.nil? @args.map(&:create_param).unshift("").join(", ") end def untyped_args return if @args.nil? @args.map(&:name).unshift("").join(", ") end def map_fields_with_size(prefix, size, &block) args = [Argument.new("opcodeID", :unsigned, 0)] args += @args.dup if @args unless @metadata.empty? args << @metadata.emitter_local end args.map { |arg| "#{prefix}#{block.call(arg, size)}" } end def struct <<-EOF struct #{capitalized_name} : public Instruction { #{opcodeID} #{emitter} #{dumper} #{constructors} #{setters}#{metadata_struct_and_accessor} #{members} }; EOF end def opcodeID "static constexpr OpcodeID opcodeID = #{name};" end def emitter op_wide = Argument.new("op_wide", :unsigned, 0) metadata_param = @metadata.empty? ? "" : ", #{@metadata.emitter_local.create_param}" metadata_arg = @metadata.empty? ? "" : ", #{@metadata.emitter_local.name}" <<-EOF.chomp static void emit(BytecodeGenerator* gen#{typed_args}) { #{@metadata.create_emitter_local} emit(gen#{untyped_args}#{metadata_arg}) || emit(gen#{untyped_args}#{metadata_arg}); } #{%{ template static bool emit(BytecodeGenerator* gen#{typed_args}) {#{@metadata.create_emitter_local} return emit(gen#{untyped_args}#{metadata_arg}); } } unless @metadata.empty?} template static bool emit(BytecodeGenerator* gen#{typed_args}#{metadata_param}) { bool didEmit = emitImpl(gen#{untyped_args}#{metadata_arg}); if (shouldAssert == Assert) ASSERT(didEmit); return didEmit; } private: template static bool emitImpl(BytecodeGenerator* gen#{typed_args}#{metadata_param}) { if (size == OpcodeSize::Wide) gen->alignWideOpcode(); if (#{map_fields_with_size("", "size", &:fits_check).join "\n && "} && (size == OpcodeSize::Wide ? #{op_wide.fits_check(Size::Narrow)} : true)) { if (recordOpcode) gen->recordOpcode(opcodeID); if (size == OpcodeSize::Wide) #{op_wide.fits_write Size::Narrow} #{map_fields_with_size(" ", "size", &:fits_write).join "\n"} return true; } return false; } public: EOF end def dumper <<-EOF template void dump(BytecodeDumper* dumper, InstructionStream::Offset __location, bool __isWide) { dumper->printLocationAndOp(__location, &"*#{@name}"[!__isWide]); #{print_args { |arg| <<-EOF.chomp dumper->dumpOperand(#{arg.field_name}, #{arg.index == 1}); EOF }} } EOF end def constructors fields = (@args || []) + (@metadata.empty? ? [] : [@metadata]) init = ->(size) { fields.empty? ? "" : ": #{fields.map.with_index { |arg, i| arg.load_from_stream(i + 1, size) }.join "\n , " }" } <<-EOF #{capitalized_name}(const uint8_t* stream) #{init.call("OpcodeSize::Narrow")} { ASSERT_UNUSED(stream, stream[0] == opcodeID); } #{capitalized_name}(const uint32_t* stream) #{init.call("OpcodeSize::Wide")} { ASSERT_UNUSED(stream, stream[0] == opcodeID); } static #{capitalized_name} decode(const uint8_t* stream) { if (*stream != op_wide) return { stream }; auto wideStream = bitwise_cast(stream + 1); return { wideStream }; } EOF end def setters print_args(&:setter) end def metadata_struct_and_accessor <<-EOF.chomp #{@metadata.struct(self)}#{@metadata.accessor} EOF end def members <<-EOF.chomp #{print_members(" ", &:field)}#{@metadata.field(" ")} EOF end def set_entry_address(id) "setEntryAddress(#{id}, _#{full_name})" end def set_entry_address_wide(id) "setEntryAddressWide(#{id}, _#{full_name}_wide)" end def struct_indices out = [] out += @args.map(&:field_name) unless @args.nil? out << Metadata.field_name unless @metadata.empty? out.map.with_index do |name, index| "const unsigned #{capitalized_name}_#{name}_index = #{index + 1};" end end def full_name "#{@section.config[:asm_prefix]}#{@section.config[:op_prefix]}#{@name}" end def name "#{@section.config[:op_prefix]}#{@name}" end def length 1 + (@args.nil? ? 0 : @args.length) + (@metadata.empty? ? 0 : 1) end def self.dump_bytecode(opcodes) <<-EOF.chomp template static void dumpBytecode(BytecodeDumper* dumper, InstructionStream::Offset __location, const Instruction* __instruction) { switch (__instruction->opcodeID()) { #{opcodes.map { |op| <<-EOF.chomp case #{op.name}: __instruction->as<#{op.capitalized_name}>().dump(dumper, __location, __instruction->isWide()); break; EOF }.join "\n"} default: ASSERT_NOT_REACHED(); } } EOF end end