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