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