• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env ruby
2
3# Copyright (c) 2021-2022 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("    bb_#{@basic_blocks.last.index}->SetNextLoop((*cur_bb)->GetNextLoop());")
285    Output.println("    (*cur_bb)->SetNextLoop(nullptr);")
286    Output.println("    for (auto succ : bb_#{@basic_blocks.last.index}->GetSuccsBlocks()) {")
287    Output.println("        auto *loop = succ->GetLoop();")
288    Output.println("        if (succ->IsLoopHeader() && loop->GetPreHeader() == *cur_bb) {")
289    Output.println("            loop->SetPreHeader(bb_#{@basic_blocks.last.index});")
290    Output.println("        }")
291    Output.println("    }")
292    Output.println("}")
293    @basic_blocks[0...-1].each { |bb|
294      Output.println("auto* bb_#{bb.index} = graph->CreateEmptyBlock();")
295      Output.println("ASSERT(bb_#{bb.index}->GetGuestPc() == INVALID_PC);")
296      Output.println("bb_#{bb.index}->SetGuestPc(pc);")
297      Output.println("bb_#{bb.index}->CopyTryCatchProps(*cur_bb);")
298      Output.println("if ((*cur_bb)->IsMarked(marker)) {")
299      Output.println("    bb_#{bb.index}->SetMarker(marker);")
300      Output.println("}")
301    } if @basic_blocks.size > 1
302    Output.println("(*cur_bb)->AddSucc(bb_#{@basic_blocks.first.index});")
303    @basic_blocks.each do |bb|
304      name = "bb_#{bb.index}"
305      Output.println("#{name}->AddSucc(bb_#{bb.true_succ.index});") if bb.true_succ
306      Output.println("#{name}->AddSucc(bb_#{bb.false_succ.index});") if bb.false_succ
307    end
308    Output.printlni('if ((*cur_bb)->GetLoop() != nullptr) {')
309    Output.printlni('if (need_new_last_bb) {')
310    Output.println("(*cur_bb)->GetLoop()->AppendBlock(bb_#{@basic_blocks.last.index});")
311    Output.printlnd('}')
312    @basic_blocks[0...-1].each { |bb| Output.println("(*cur_bb)->GetLoop()->AppendBlock(bb_#{bb.index});") }
313    Output.printlnd('}')
314    Output.println("(*cur_bb) = bb_#{@basic_blocks.last.index};")
315    @basic_blocks.each(&:generate_builder)
316    Output.printlnd('}')
317  end
318
319  def self.setup
320    InstructionsData.instructions.each do |name, dscr|
321      # Some of the opcodes were already defined manually, e.g. Intrinsic
322      next if method_defined? name
323
324      define_method(name) do |*inputs, &block|
325        raise "Current basic block is nil" if @current_block.nil?
326        inst = create_instruction(name, inputs)
327        inst.annotation = Kernel.send(:caller)[0].split(':in')[0]
328        caller_data = caller_locations[0]
329        file_path = File.expand_path(caller_data.path)
330        inst.debug_info.dir = add_source_dir(File.dirname(file_path))
331        inst.debug_info.file = add_source_file(File.basename(file_path))
332        inst.debug_info.line = caller_data.lineno
333        process_inst(inst, &block)
334        inst
335      end
336    end
337  end
338
339  def Intrinsic(name, *inputs)
340    inst = create_instruction(:Intrinsic, inputs).IntrinsicId("RuntimeInterface::IntrinsicId::INTRINSIC_#{name}")
341    caller_data = caller_locations[0]
342    file_path = File.expand_path(caller_data.path)
343    inst.debug_info.dir = add_source_dir(File.dirname(file_path))
344    inst.debug_info.file = add_source_file(File.basename(file_path))
345    inst.debug_info.line = caller_data.lineno
346    process_inst(inst)
347    inst
348  end
349
350  def Label(name)
351    last_bb = @current_block
352    new_basic_block
353    last_bb.set_true_succ(@current_block) if last_bb.true_succ.nil? && !last_bb.terminator?
354    label = @labels.last[name]
355    if label
356      label.bb = @current_block
357      label.refs.each { |bb| bb.set_true_succ(@current_block) }
358    else
359      @labels.last[name] = Label.new(name, @current_block)
360    end
361  end
362
363  def Goto(name)
364    if @labels.last.key? name
365      label = @labels.last[name]
366      if label.bb
367        @current_block.set_true_succ(label.bb)
368      else
369        label.refs << @current_block
370      end
371    else
372      label = @labels.last[name] = Label.new(name)
373      label.refs << @current_block
374    end
375    cblock = @cf_stack[-1]
376    cblock.goto = true unless cblock.nil?
377    process_inst nil
378  end
379
380  def LiveIn(value, type = 'ptr')
381    value = regmap[value] if value.is_a?(Symbol)
382    res = @registers[value]
383    return res unless res.nil?
384    inst = create_instruction(:LiveIn).DstReg(value)
385    inst.send(type)
386    @registers[value] = inst
387  end
388
389  def resolve_variables
390    @instructions.each do |inst|
391      inst.inputs.each_with_index do |input, i|
392        if input.is_a? UnresolvedVar
393          raise "Function: #{@name} Unresolved variable: #{input.name}" if input.inst.nil?
394          inst.inputs[i] = input.inst
395        elsif input.nil?
396          raise "Input is nil for: #{inst}, input"
397        end
398      end
399    end
400
401    @unresolved.last.each_pair do |name, var|
402      raise "Variable cannot be resolved: #{name}" if var.inst.nil?
403    end
404  end
405
406  def get_if_block(else_depth)
407    @cf_stack.reverse.each do |block|
408      return block if block.depth == else_depth && block.kind == CfBlock::Kind::IfThen
409    end
410    raise "`If` block not found"
411  end
412
413  def process_inst(inst, &block)
414    if !block.nil?
415      @current_depth += 1
416      cblock = CfBlock.new(CfBlock::get_kind(inst), @current_depth)
417      cblock.head_bb = @current_block
418      @cf_stack << cblock
419      new_basic_block() unless @current_block.empty?
420
421      if inst.IsIf? || inst.IsIfImm? || inst.IsWhile? || inst.IsAddOverflow? || inst.IsSubOverflow?
422        cblock.head_bb.set_true_succ(@current_block)
423      elsif inst.IsElse?
424        get_if_block(@current_depth).head_bb.set_false_succ(@current_block)
425      end
426
427      instance_eval &block
428      @current_depth -= 1
429
430      cblock.bb = @current_block
431      new_basic_block()
432
433    else
434      last_block = @cf_stack[-1]
435      return if !last_block.nil? && @current_depth == last_block.depth && last_block.kind == CfBlock::Kind::IfElse
436      @cf_stack.delete_if do |block|
437        next if block.depth <= @current_depth
438
439        if block.bb.true_succ.nil?
440          if block.kind == CfBlock::Kind::While
441            block.bb.set_true_succ(block.head_bb)
442          else
443            # If last instruction in the block is a terminator(e.g. Return), then don't set true successor, hence it
444            # will point to the end block, that is required by IR design.
445            block.bb.set_true_succ(@current_block) unless !block.bb.empty? && block.bb.last_instruction.terminator?
446          end
447        end
448
449
450        if block.head_bb.false_succ.nil? && (block.kind == CfBlock::Kind::IfThen || block.kind == CfBlock::Kind::While)
451          block.head_bb.set_false_succ(@current_block)
452        end
453
454        true
455      end
456    end
457  end
458
459  def new_basic_block
460    @bb_index ||= 2
461    @current_block = BasicBlock.new(@bb_index, self)
462    @bb_index += 1
463    @basic_blocks << @current_block
464    @current_block
465  end
466
467  def add_source_dir(dir)
468    index = @source_dirs.index(dir)
469    return index unless index.nil?
470    @source_dirs << dir
471    @source_dirs.size - 1
472  end
473
474  def add_source_file(filename)
475    raise "File name must be a string" unless filename.is_a? String
476    index = @source_files.index(filename)
477    return index unless index.nil?
478    @source_files << filename
479    @source_files.size - 1
480  end
481
482  # leave only inputs that are defined (pass inputs that can be missing as symbols)
483  def load_phi_inputs(*inputs)
484    inputs.reject { |x| (x.is_a? Symbol) && (!respond_to? x) }.map { |x| (x.is_a? Symbol) ? send(x) : x }
485  end
486
487end
488