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_relative 'basic_block'
17require_relative 'instruction'
18require_relative 'output'
19require_relative 'regmask'
20
21class CfBlock
22  attr_accessor :bb, :head_bb, :goto
23  attr_reader :kind, :depth
24
25  module Kind
26    IfThen = 0
27    IfElse = 1
28    While = 2
29  end
30
31  def initialize(kind, depth)
32    @kind = kind
33    @depth = depth
34    @head_bb = nil
35    @bb = nil
36    @goto = false
37  end
38
39  def set_succ(bb)
40    if @kind == Kind::While
41      @bb.set_false_succ(bb)
42    else
43      @bb.set_true_succ(bb)
44    end
45  end
46
47  def self.get_kind(inst)
48    @@opc_map ||= { If: Kind::IfThen, IfImm: Kind::IfThen, Else: Kind::IfElse, While: Kind::While, AddOverflow: Kind::IfThen, SubOverflow: Kind::IfThen }
49    @@opc_map[inst.opcode]
50  end
51end
52
53class UnresolvedVar
54  attr_reader :name
55  attr_accessor :inst
56
57  def initialize(name)
58    @name = name.to_sym
59    @inst = nil
60  end
61end
62
63class Label
64  attr_reader :refs, :name
65  attr_accessor :bb
66
67  def initialize(name, bb=nil)
68    @name = name
69    @bb = bb
70    @refs = []
71  end
72end
73
74class Function
75
76  attr_reader :name, :mode, :validation, :enable_builder, :external_funcs, :source_dirs, :source_files, :arguments, :params, :constants, :basic_blocks
77
78  def initialize(name, params: {}, regmap: {}, mode: nil, regalloc_set: nil, regalloc_include: nil, regalloc_exclude: nil,
79                 validate: nil, enable_builder: false, lang: 'PANDA_ASSEMBLY', compile_for_llvm: false, &block)
80    @name = name
81    @mode = mode
82    @lang = lang
83    @compile_for_llvm = compile_for_llvm
84    @regalloc_mask = regalloc_set ? regalloc_set : $default_mask
85    @regalloc_mask |= regalloc_include unless regalloc_include.nil?
86    @regalloc_mask &= ~regalloc_exclude unless regalloc_exclude.nil?
87
88    @instructions = []
89    @basic_blocks = []
90    @locals = [{}]
91    @current_depth = 0
92    @unresolved = [{}]
93    @cf_stack = []
94    @labels = [{}]
95    if validate
96      @validation = {}
97      validate.each do |k, v|
98        if v.is_a?(Hash)
99          value = v[Options.arch.to_sym]
100          value = v[:default] unless value
101        else
102          value = v
103        end
104        @validation[k] = value
105      end
106
107      @validation.each { |name, v| raise "#{@name}: no target arch in validation value: #{name}" unless v }
108    end
109    @body = block
110    @external_funcs = []
111    @enable_builder = enable_builder
112    @source_dirs = []
113    @source_files = []
114
115    self.define_singleton_method(:regmap) { regmap }
116    # Lists of special/global instructions
117    @arguments = []
118    @registers = {}
119    @constants = {}
120
121    new_basic_block()
122
123    # Process function's arguments
124    @params = params
125    params.each_with_index do |(name, type), i|
126      inst = create_instruction(:Parameter)
127      inst.send(type)
128      inst.set_parameter_index(i)
129      @arguments << inst
130      let(name, inst)
131    end
132  end
133
134  # Return true if graph doesn't contain conditional basic block, i.e. with more than one successor.
135  def simple_control_flow?
136    @result ||= @basic_blocks.none? { |bb| bb.true_succ && bb.false_succ }
137  end
138
139  def compile
140    Options.compiling = true
141    instance_eval &@body
142    Options.compiling = false
143    resolve_variables
144    @basic_blocks.delete_if { |bb| bb.empty? && bb.preds.empty? }
145  end
146
147  def defines
148    Options.definitions
149  end
150
151  def create_instruction(opcode, inputs = [])
152    if opcode == :WhilePhi
153      raise "Wrong place of `WhilePhi` instruction" unless @cf_stack[-1].kind == CfBlock::Kind::While
154      raise "Invalid `While` block" if @cf_stack[-1].head_bb.nil?
155      block = @cf_stack[-1].head_bb
156    else
157      block = @current_block
158    end
159    inst = IRInstruction.new(opcode, inst_index(), block)
160    inst.add_inputs(inputs.map { |input|
161      (input.is_a? Integer or input.is_a? Float or input.is_a? String) ? get_or_create_constant(input) : input
162    })
163    @instructions << inst unless inst.IsElse?
164    block.append inst unless inst.global?
165    inst
166  end
167
168  def get_or_create_constant(value)
169    constant = @constants[value]
170    return @constants[value] unless constant.nil?
171    @constants[value] = IRInstruction.new(:Constant, inst_index(), @current_block, Value: value).i64
172  end
173
174  def let(var_name, inst)
175    if inst.is_a? Integer or inst.is_a? String
176      inst = get_or_create_constant(inst)
177    end
178    resolved = @unresolved.last[var_name.to_sym]
179    if resolved
180      raise "Unresolved variable is defined more than once: #{var_name}" unless resolved.inst.nil?
181      resolved.inst = inst
182    end
183    @locals.last[var_name.to_sym] = inst
184    self.define_singleton_method(var_name.to_sym) do
185      r = @locals.last[var_name.to_sym]
186      return r unless r.nil?
187      var = UnresolvedVar.new(var_name.to_sym)
188      @unresolved.last[var_name.to_sym] = var
189      var
190    end
191    inst
192  end
193
194  def global(var_sym)
195    self.define_singleton_method(var_sym) do
196      scope = @locals.reverse_each.detect { |scope| !scope[var_sym].nil? }
197      return scope[var_sym] unless scope.nil?
198      var = UnresolvedVar.new(var_sym)
199      @unresolved.last[var_sym] = var
200      var
201    end
202  end
203
204  def inst_index
205    @inst_index ||= 0
206    @inst_index += 1
207    @inst_index - 1
208  end
209
210  def method_missing(method, *args, &block)
211    if Options.compiling && args.empty? && block.nil?
212      method = method.to_sym
213      var = UnresolvedVar.new(method)
214      @unresolved.last[method] = var
215      var
216    else
217      super
218    end
219  end
220
221  def emit_ir(suffix)
222    Output.printlni("COMPILE(#{@name}#{suffix}) {")
223    raise "Compilation mode is not specified" unless @mode
224    Output.println("if(GetGraph()->GetArch() != #{Options.cpp_arch}) LOG(FATAL, IRTOC) << \"Arch doesn't match\";")
225    if suffix == "_LLVM"
226      Output.println("GetGraph()->SetIrtocPrologEpilogOptimized();")
227    end
228    Output.println("GetGraph()->SetRelocationHandler(this);")
229    Output.println("SetLanguage(SourceLanguage::#{@lang});")
230    Output.println("[[maybe_unused]] auto *graph = GetGraph();")
231    Output.println("[[maybe_unused]] auto *runtime = GetGraph()->GetRuntime();")
232    Output.println("SetArgsCount(#{@params.length});")
233    if @mode
234      ss = "GetGraph()->SetMode(GraphMode(0)"
235      @mode.each do |m|
236        ss += " | GraphMode::#{m.to_s}(true)"
237      end
238      ss += ");"
239      Output.println(ss)
240    end
241    if @regalloc_mask
242      Output.println("GetGraph()->SetArchUsedRegs(~0x#{@regalloc_mask.value.to_s(16)});")
243      Output << "// Regalloc mask: #{@regalloc_mask}"
244    end
245    Output.println(%Q[SetExternalFunctions({"#{@external_funcs.join('", "')}"});]) if @external_funcs
246    @source_dirs.each_with_index do |dir, index|
247      Output.println("uint32_t DIR_#{index} = AddSourceDir(\"#{dir}\");")
248    end
249    @source_files.each_with_index do |file, index|
250      Output.println("uint32_t FILE_#{index} = AddSourceFile(\"#{file}\");")
251    end
252    Output.printlni("GRAPH(GetGraph()) {")
253    @arguments.each { |inst| inst.emit_ir }
254    @constants.each { |_, inst| inst.emit_ir }
255    @registers.each { |_, inst| inst.emit_ir }
256    @basic_blocks.each {|bb| bb.emit_ir }
257    Output.printlnd("}")
258    Output.printlnd("}")
259  end
260
261  def generate_builder
262    params = "Graph* graph, [[maybe_unused]] InstBuilder *inst_builder, BasicBlock** cur_bb, #{@params.keys.map.with_index { |_, i| "Inst* p_#{i}" }.join(', ')}"
263    params += ', ' unless @params.empty?
264    params += 'size_t pc, Marker marker'
265    Output.println("// NOLINTNEXTLINE(readability-function-size)")
266    Output.printlni("[[maybe_unused]] static Inst* Build#{@name}(#{params}) {")
267    Output.println("[[maybe_unused]] auto *runtime = graph->GetRuntime();")
268    @constants.each { |_, inst| inst.generate_builder }
269    Output.println("auto need_new_last_bb = (*cur_bb)->IsEmpty();  // create an empty BB or extract from an existing one?")
270    Output.println("auto* bb_#{@basic_blocks.last.index} = need_new_last_bb ? graph->CreateEmptyBlock() : (*cur_bb)->SplitBlockAfterInstruction((*cur_bb)->GetLastInst(), false);")
271    Output.println("ASSERT(bb_#{@basic_blocks.last.index}->GetGuestPc() == INVALID_PC);")
272    Output.println("bb_#{@basic_blocks.last.index}->SetGuestPc(pc);")
273    Output.println("bb_#{@basic_blocks.last.index}->CopyTryCatchProps(*cur_bb);")
274    Output.println("if ((*cur_bb)->IsMarked(marker)) {")
275    Output.println("    bb_#{@basic_blocks.last.index}->SetMarker(marker);")
276    Output.println("}")
277    Output.println("if (need_new_last_bb) {")
278    Output.println("    for (auto succ : (*cur_bb)->GetSuccsBlocks()) {")
279    Output.println("        succ->ReplacePred(*cur_bb, bb_#{@basic_blocks.last.index});")
280    Output.println("    }")
281    Output.println("    (*cur_bb)->GetSuccsBlocks().clear();")
282    Output.println("}")
283    Output.println("if ((*cur_bb)->IsLoopPreHeader()) {")
284    Output.println("    (*cur_bb)->GetNextLoop()->SetPreHeader(bb_#{@basic_blocks.last.index});")
285    Output.println("}")
286    @basic_blocks[0...-1].each { |bb|
287      Output.println("auto* bb_#{bb.index} = graph->CreateEmptyBlock();")
288      Output.println("ASSERT(bb_#{bb.index}->GetGuestPc() == INVALID_PC);")
289      Output.println("bb_#{bb.index}->SetGuestPc(pc);")
290      Output.println("bb_#{bb.index}->CopyTryCatchProps(*cur_bb);")
291      Output.println("if ((*cur_bb)->IsMarked(marker)) {")
292      Output.println("    bb_#{bb.index}->SetMarker(marker);")
293      Output.println("}")
294    } if @basic_blocks.size > 1
295    Output.println("(*cur_bb)->AddSucc(bb_#{@basic_blocks.first.index});")
296    @basic_blocks.each do |bb|
297      name = "bb_#{bb.index}"
298      Output.println("#{name}->AddSucc(bb_#{bb.true_succ.index});") if bb.true_succ
299      Output.println("#{name}->AddSucc(bb_#{bb.false_succ.index});") if bb.false_succ
300    end
301    Output.printlni('if ((*cur_bb)->GetLoop() != nullptr) {')
302    Output.printlni('if (need_new_last_bb) {')
303    Output.println("(*cur_bb)->GetLoop()->AppendBlock(bb_#{@basic_blocks.last.index});")
304    Output.printlnd('}')
305    @basic_blocks[0...-1].each { |bb| Output.println("(*cur_bb)->GetLoop()->AppendBlock(bb_#{bb.index});") }
306    Output.printlnd('}')
307    Output.println("(*cur_bb) = bb_#{@basic_blocks.last.index};")
308    @basic_blocks.each(&:generate_builder)
309    Output.printlnd('}')
310  end
311
312  def self.setup
313    InstructionsData.instructions.each do |name, dscr|
314      # Some of the opcodes were already defined manually, e.g. Intrinsic
315      next if method_defined? name
316
317      define_method(name) do |*inputs, &block|
318        raise "Current basic block is nil" if @current_block.nil?
319        inst = create_instruction(name, inputs)
320        inst.annotation = Kernel.send(:caller)[0].split(':in')[0]
321        caller_data = caller_locations[0]
322        file_path = File.expand_path(caller_data.path)
323        inst.debug_info.dir = add_source_dir(File.dirname(file_path))
324        inst.debug_info.file = add_source_file(File.basename(file_path))
325        inst.debug_info.line = caller_data.lineno
326        process_inst(inst, &block)
327        inst
328      end
329    end
330  end
331
332  def Intrinsic(name, *inputs)
333    inst = create_instruction(:Intrinsic, inputs).IntrinsicId("RuntimeInterface::IntrinsicId::INTRINSIC_#{name}")
334    caller_data = caller_locations[0]
335    file_path = File.expand_path(caller_data.path)
336    inst.debug_info.dir = add_source_dir(File.dirname(file_path))
337    inst.debug_info.file = add_source_file(File.basename(file_path))
338    inst.debug_info.line = caller_data.lineno
339    process_inst(inst)
340    inst
341  end
342
343  def Label(name)
344    last_bb = @current_block
345    new_basic_block
346    last_bb.set_true_succ(@current_block) if last_bb.true_succ.nil? && !last_bb.terminator?
347    label = @labels.last[name]
348    if label
349      label.bb = @current_block
350      label.refs.each { |bb| bb.set_true_succ(@current_block) }
351    else
352      @labels.last[name] = Label.new(name, @current_block)
353    end
354  end
355
356  def Goto(name)
357    if @labels.last.key? name
358      label = @labels.last[name]
359      if label.bb
360        @current_block.set_true_succ(label.bb)
361      else
362        label.refs << @current_block
363      end
364    else
365      label = @labels.last[name] = Label.new(name)
366      label.refs << @current_block
367    end
368    cblock = @cf_stack[-1]
369    cblock.goto = true unless cblock.nil?
370    process_inst nil
371  end
372
373  def LiveIn(value, type = 'ptr')
374    value = regmap[value] if value.is_a?(Symbol)
375    res = @registers[value]
376    return res unless res.nil?
377    inst = create_instruction(:LiveIn).DstReg(value)
378    inst.send(type)
379    @registers[value] = inst
380  end
381
382  def resolve_variables
383    @instructions.each do |inst|
384      inst.inputs.each_with_index do |input, i|
385        if input.is_a? UnresolvedVar
386          raise "Function: #{@name} Unresolved variable: #{input.name}" if input.inst.nil?
387          inst.inputs[i] = input.inst
388        elsif input.nil?
389          raise "Input is nil for: #{inst}, input"
390        end
391      end
392    end
393
394    @unresolved.last.each_pair do |name, var|
395      raise "Variable cannot be resolved: #{name}" if var.inst.nil?
396    end
397  end
398
399  def get_if_block(else_depth)
400    @cf_stack.reverse.each do |block|
401      return block if block.depth == else_depth && block.kind == CfBlock::Kind::IfThen
402    end
403    raise "`If` block not found"
404  end
405
406  def process_inst(inst, &block)
407    if !block.nil?
408      @current_depth += 1
409      cblock = CfBlock.new(CfBlock::get_kind(inst), @current_depth)
410      cblock.head_bb = @current_block
411      @cf_stack << cblock
412      new_basic_block() unless @current_block.empty?
413
414      if inst.IsIf? || inst.IsIfImm? || inst.IsWhile? || inst.IsAddOverflow? || inst.IsSubOverflow?
415        cblock.head_bb.set_true_succ(@current_block)
416      elsif inst.IsElse?
417        get_if_block(@current_depth).head_bb.set_false_succ(@current_block)
418      end
419
420      instance_eval &block
421      @current_depth -= 1
422
423      cblock.bb = @current_block
424      new_basic_block()
425
426    else
427      last_block = @cf_stack[-1]
428      return if !last_block.nil? && @current_depth == last_block.depth && last_block.kind == CfBlock::Kind::IfElse
429      @cf_stack.delete_if do |block|
430        next if block.depth <= @current_depth
431
432        if block.bb.true_succ.nil?
433          if block.kind == CfBlock::Kind::While
434            block.bb.set_true_succ(block.head_bb)
435          else
436            # If last instruction in the block is a terminator(e.g. Return), then don't set true successor, hence it
437            # will point to the end block, that is required by IR design.
438            block.bb.set_true_succ(@current_block) unless !block.bb.empty? && block.bb.last_instruction.terminator?
439          end
440        end
441
442
443        if block.head_bb.false_succ.nil? && (block.kind == CfBlock::Kind::IfThen || block.kind == CfBlock::Kind::While)
444          block.head_bb.set_false_succ(@current_block)
445        end
446
447        true
448      end
449    end
450  end
451
452  def new_basic_block
453    @bb_index ||= 2
454    @current_block = BasicBlock.new(@bb_index, self)
455    @bb_index += 1
456    @basic_blocks << @current_block
457    @current_block
458  end
459
460  def add_source_dir(dir)
461    index = @source_dirs.index(dir)
462    return index unless index.nil?
463    @source_dirs << dir
464    @source_dirs.size - 1
465  end
466
467  def add_source_file(filename)
468    raise "File name must be a string" unless filename.is_a? String
469    index = @source_files.index(filename)
470    return index unless index.nil?
471    @source_files << filename
472    @source_files.size - 1
473  end
474
475  # leave only inputs that are defined (pass inputs that can be missing as symbols)
476  def load_phi_inputs(*inputs)
477    inputs.reject { |x| (x.is_a? Symbol) && (!respond_to? x) }.map { |x| (x.is_a? Symbol) ? send(x) : x }
478  end
479
480end
481