1#!/usr/bin/env ruby
2
3# Copyright (c) 2021-2024 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16require "ostruct"
17require_relative 'instructions_data'
18require_relative 'output'
19
20class InstIsaDescription
21  def initialize(inst, dscr)
22    @inst = inst
23    @dscr = dscr
24  end
25
26  def pseudo?
27    @dscr.flags.include? "pseudo"
28  end
29
30  def has_dst?
31    return false if pseudo?
32    !@dscr.signature.empty? && @dscr.signature[0].split('-').include?('d')
33  end
34end
35
36class IRInstruction
37  attr_reader :index, :inputs, :name, :bb, :fields, :modifiers, :dscr
38  attr_accessor :type, :annotation, :debug_info
39
40  DebugInfo = Struct.new(:dir, :file, :line)
41
42  def initialize(name, index, bb, **kwargs)
43    @name = name
44    @index = index
45    @bb = bb
46    @dscr = OpenStruct.new(InstructionsData.instructions[name])
47    @fields = kwargs
48    @inputs = []
49    @type = nil
50    @modifiers = []
51    @debug_info = DebugInfo.new
52    abort "Wrong instructions #{name}" unless InstructionsData.instructions.include? name
53  end
54
55  def set_parameter_index(index)
56    raise "Trying to set argument index for non Parameter instruction" unless IsParameter?
57    @fields[:ArgIndex] = index
58  end
59
60  def no_dce
61    SetFlag('inst_flags::NO_DCE')
62  end
63
64  def set_access_type(type)
65    SetAccessType("DynObjectAccessType::#{type.to_s.upcase}")
66  end
67
68  def set_access_mode(mode)
69    SetAccessMode("DynObjectAccessMode::#{mode.to_s.upcase}")
70  end
71
72  def global?
73    return IsConstant? || IsLiveIn? || IsElse? || IsParameter?
74  end
75
76  def terminator?
77    IsReturn? || IsReturnVoid? || IsThrow? || has_modifier?(:Terminator)
78  end
79
80  def has_modifier?(mod)
81    @modifiers.any? { |x| x[0] == mod}
82  end
83
84  def opcode
85    @name
86  end
87
88  def pseudo?
89    @dscr.flags.include? "pseudo"
90  end
91
92  def dynamic_inputs?
93    @dscr.signature&.any? { |operand| operand.end_with?('-dyn') }
94  end
95
96  def add_inputs(insts)
97    @inputs += insts
98  end
99
100  def mw(&block)
101    @type = "mw"
102    if !block.nil?
103      @bb.function.process_inst(self, &block)
104    end
105    self
106  end
107
108  def ref_uint(&block)
109    @type = "ref_uint"
110    if !block.nil?
111      @bb.function.process_inst(self, &block)
112    end
113    self
114  end
115
116  def method_missing(method, *args, **kwargs, &block)
117    if Options.compiling
118      @modifiers << [method, args]
119      @bb.function.process_inst(self, &block) unless block.nil?
120      self
121    else
122      super
123    end
124  end
125
126  def Method(name, setter = :TypeId)
127    index = @bb.function.external_funcs.index(name)
128    if index.nil?
129        index = @bb.function.external_funcs.size
130        @bb.function.external_funcs << name
131    end
132    send(setter, index)
133  end
134
135  def MethodAsImm(name)
136    index = @bb.function.external_funcs.index(name)
137    if index.nil?
138        index = @bb.function.external_funcs.size
139        @bb.function.external_funcs << name
140    end
141    AddImm(index)
142  end
143
144  def emit_ir
145    opc = opcode()
146    Output << "// #{self.to_s}"
147    Output << "// #{self.annotation}"
148    if IsConstant?()
149      ss = "CONSTANT(#{@index}, #{@fields[:Value]})"
150    elsif IsParameter?()
151      Output.println "PARAMETER(#{@index}, #{@fields[:ArgIndex]}).#{@type}();"
152      return
153    elsif IsElse?
154      return
155    else
156      opc = :If if IsWhile?
157      opc = :Phi if IsWhilePhi?
158      ss = "INST(#{@index}, Opcode::#{opc})"
159    end
160
161    if IsIntrinsic? || IsCallIndirect? || IsCall? || IsCallDynamic?
162      inputs = @inputs.map do |input|
163        t = input.type == "mw" ? "IrConstructor::MARK_WORD_TYPE" : "DataType::#{input.get_type_for_cpp}"
164        "{#{t}, #{input.index}}"
165      end.join(", ")
166      ss += ".Inputs({#{inputs}})" unless inputs.empty?
167    else
168      raise "Instruction has unresolved inputs: #{self}" if @inputs.any? {|x| x.nil? }
169      inputs = @inputs.map(&:index).join(", ")
170      ss += ".Inputs(#{inputs})" unless inputs.empty?
171    end
172
173    type = @type == :void ? :v0id : @type
174    ss += ".#{type}()" unless type.nil?
175    @modifiers.each do |mod|
176      ss += ".#{modifier_to_s(mod)}"
177    end
178    ss += ".Loc(DIR_#{@debug_info.dir}, FILE_#{@debug_info.file}, #{@debug_info.line})" if @debug_info.line
179    ss += ';'
180    Output.println ss
181  end
182
183  def to_s
184    "Inst(id=#{@index}, opc=#{@name})"
185  end
186
187  def inspect
188    to_s
189  end
190
191  def get_type_for_cpp
192    @@type_map ||= {
193      nil   => :NO_TYPE,
194      :i8   => :INT8,
195      :i16  => :INT16,
196      :i32  => :INT32,
197      :i64  => :INT64,
198      :u8   => :UINT8,
199      :u16  => :UINT16,
200      :u32  => :UINT32,
201      :u64  => :UINT64,
202      :f32  => :FLOAT32,
203      :f64  => :FLOAT64,
204      :b    => :BOOL,
205      :ref  => :REFERENCE,
206      :ptr  => :POINTER,
207      :void => :VOID,
208      :any  => :ANY,
209      :ref_uint => :'GetIntTypeForReference(GetGraph()->GetArch())'
210    }
211    res = @@type_map[@type&.to_sym]
212    raise "Wrong type: #{@type}" if res.nil?
213    res
214  end
215
216  def self.setup
217    InstructionsData.types[:word] = nil
218    InstructionsData.types[:sword] = nil
219    InstructionsData.types.each do |name, _|
220      name = name == 'bool' ? :b : name.to_sym
221      define_method(name) do |&block|
222        @type = name == :word ? (Options.arch_64_bits? ? 'u64' : 'u32') :
223               (name == :sword ? (Options.arch_64_bits? ? 'i64' : 'i32') : name)
224        @bb.function.process_inst(self, &block) if !block.nil?
225        self
226      end
227    end
228
229    InstructionsData.instructions.each do |opcode, inst|
230      define_method("Is#{opcode}?".to_sym) do
231        @name == inst['opcode'].to_sym
232      end
233    end
234
235    # Generate concise functions for creating condition code: If().CC(:CC_EQ) => If().EQ
236    [:EQ, :NE, :GE, :GT, :LE, :LT, :A, :AE, :B, :BE].each do |x| define_method(x) do |&block|
237        send(:CC, "CC_#{x}".to_sym, &block)
238        self
239      end
240    end
241  end
242
243  def generate_builder
244    Output << "// #{self.to_s}"
245    Output << "// #{self.annotation}"
246    # TODO(mbolshov): raise 'No LiveIn/LiveOut are allowed in IR Builder generator' if IsLiveIn? || IsLiveOut?
247    if IsReturnVoid?
248      Output.println('return nullptr;')
249    elsif IsReturn?
250      raise "Return has #{@inputs.size}" if @inputs.size != 1
251
252      Output.println("return #{@inputs.first.local_var_name};")
253    elsif IsConstant?
254      Output.println("// NOLINTNEXTLINE(readability-magic-numbers)")
255      Output.println("auto* #{local_var_name} = graph->FindOrCreateConstant(#{@fields[:Value]});")
256    else
257      if IsCall?
258        index = @modifiers.detect {|mod| mod[0] == :TypeId}[1][0]
259        name = @bb.function.external_funcs[index].snakecase
260        intrinsic_id = name.split('_')[0..-2].join('_').upcase
261        intrinsic_id = "RuntimeInterface::IntrinsicId::INTRINSIC_#{intrinsic_id}"
262        Output.println("auto* #{local_var_name} = graph->CreateInstIntrinsic(DataType::#{get_type_for_cpp}, pc);")
263        Output.println("#{local_var_name}->SetIntrinsicId(#{intrinsic_id});")
264        Output.println("#{local_var_name}->SetFlag(inst_flags::CAN_THROW);")
265      else
266        Output.println("auto* #{local_var_name} = graph->CreateInst#{@name}(DataType::#{get_type_for_cpp}, pc);")
267      end
268      generate_inst_inputs
269      if IsCmp? || IsCompare? || IsIf? || IsIfImm? || IsSelect? || IsSelectImm?
270        Output.println("#{local_var_name}->SetOperandsType(DataType::#{@inputs.first.get_type_for_cpp});")
271      end
272      generate_inst_modifiers
273      if IsPhi?
274        Output.println("bb_#{@bb.index}->AppendPhi(#{local_var_name});")
275      else
276        Output.println("bb_#{@bb.index}->AppendInst(#{local_var_name});")
277      end
278    end
279  end
280
281  # name of local variable in generated IR Builder source code
282  def local_var_name
283    IsParameter? ? "p_#{@index}" : "l_#{@index}"
284  end
285
286  def modifier_to_s(mod)
287    "#{mod[0]}(#{mod[1].join(', ')})"
288  end
289
290  def generate_inst_inputs
291    need_save_state = IsCall?
292    raise 'SaveState is added only for instructions with dynamic number of inputs' if need_save_state && !dynamic_inputs?
293    num_inputs = @inputs.size + (need_save_state ? 1 : 0)
294    if dynamic_inputs?
295      Output.println("// NOLINTNEXTLINE(readability-magic-numbers)")
296      Output.println("#{local_var_name}->ReserveInputs(#{num_inputs});")
297      Output.println("#{local_var_name}->AllocateInputTypes(graph->GetAllocator(), #{num_inputs});") unless IsPhi?
298    end
299    @inputs.each_with_index do |input, i|
300      input_name = input.local_var_name
301      if dynamic_inputs?
302        Output.println("#{local_var_name}->AppendInput(#{input_name});")
303        Output.println("#{local_var_name}->AddInputType(DataType::#{input.get_type_for_cpp});") unless IsPhi?
304      else
305        Output.println("#{local_var_name}->SetInput(#{i}, #{input_name});")
306      end
307    end
308    if need_save_state
309      # SaveState is the last input by convention:
310      save_state_var_name = "#{local_var_name}_save_state"
311      Output.println("auto #{save_state_var_name} = inst_builder->CreateSaveState(Opcode::SaveState, pc);");
312      Output.println("#{local_var_name}->AppendInput(#{save_state_var_name});")
313      Output.println("#{local_var_name}->AddInputType(DataType::NO_TYPE);")
314      Output.println("bb_#{@bb.index}->AppendInst(#{save_state_var_name});")
315    end
316  end
317
318  def generate_inst_modifiers
319    @modifiers.each do |mod|
320      next if IsCall?
321
322      mod[0] = 'SetOperandsType' if mod[0] == :SrcType
323      mod[0] = 'SetCc' if mod[0] == :CC
324      prefix = 'Set' unless mod[0].to_s.start_with?('Set')
325      Output.println("// NOLINTNEXTLINE(readability-magic-numbers)")
326      Output.println("#{local_var_name}->#{prefix}#{modifier_to_s(mod)};")
327    end
328  end
329
330  def dump(stm = STDOUT)
331    mods = @modifiers.empty? ? "" : ".#{@modifiers.join('.')}"
332    type = @type.nil? ? "" : ".#{@type}"
333    stm.print("#{index}.#{@name}#{type}")
334    if !@inputs.empty?
335      inputs = @inputs.map { |x| "#{x.index}"}
336      stm.print(" (#{inputs.join(', ')})")
337    end
338    stm.print(mods)
339    if !@fields.empty?
340      stm.print(", #{@fields}")
341    end
342  end
343
344  def inspect
345    ss = StringIO.new
346    dump(ss)
347    ss.string
348  end
349
350  def to_s
351    inspect
352  end
353
354end
355