1# Copyright (c) 2021-2024 Huawei Device Co., Ltd. 2# Licensed under the Apache License, Version 2.0 (the "License"); 3# you may not use this file except in compliance with the License. 4# You may obtain a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, 10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14require 'delegate' 15require 'ostruct' 16require 'digest' 17require 'set' 18 19module Enumerable 20 def stable_sort_by 21 sort_by.with_index { |x, idx| [yield(x), idx] } 22 end 23end 24 25module Util 26 module_function 27 28 def parse_acc_signature(sig) 29 res = [] 30 if sig.include?('->') 31 src_type, dst_type = sig.match(/inout:(\w+)->(\w+)/).captures 32 res << Operand.new('acc', 'out', dst_type) 33 res << Operand.new('acc', 'in', src_type) 34 elsif sig.include?(':') 35 srcdst, type = sig.match(/(\w+):(\w+)/).captures 36 if srcdst == 'inout' 37 res << Operand.new('acc', 'out', type) 38 res << Operand.new('acc', 'in', type) 39 else 40 res << Operand.new('acc', srcdst, type) 41 end 42 elsif sig != 'none' 43 raise "Unexpected accumulator signature: #{sig}" 44 end 45 res 46 end 47 48 def parse_operand_signature(sig) 49 operand_parts = sig.split(':') 50 case operand_parts.size 51 when 1 52 name = operand_parts[0] 53 srcdst = :in 54 type = 'none' 55 when 2 56 name, type = operand_parts 57 srcdst = :in 58 when 3 59 name, srcdst, type = operand_parts 60 else 61 raise "Unexpected operand signature: #{sig}" 62 end 63 [name, srcdst, type] 64 end 65end 66 67module FreezeMixin 68 class << self 69 def included(base) 70 base.extend ClassMethods 71 end 72 end 73 74 module ClassMethods 75 def freeze_defined_methods 76 @frozen = instance_methods.map { |m| [m, true] }.to_h 77 end 78 79 def frozen?(name) 80 defined?(@frozen) && @frozen[name] 81 end 82 83 def method_added(name) 84 raise "Method '#{name}' has been already defined" if frozen?(name) 85 86 super 87 end 88 end 89end 90 91# Methods for YAML instructions 92# 'Instruction' instances are created for every format of every isa.yaml 93# instruction and inherit properties of its instruction group. 94# 95class Instruction < SimpleDelegator 96 # Signature without operands 97 def mnemonic 98 sig.split(' ')[0] 99 end 100 101 # Mnemonic stripped from type info 102 def stripped_mnemonic 103 mnemonic.split('.')[0] 104 end 105 106 # Unique (and not very long) identifier for all instructions 107 def opcode 108 mn = mnemonic.tr('.', '_') 109 fmt = format.pretty 110 if fmt == 'none' 111 mn 112 else 113 "#{mn}_#{fmt}" 114 end 115 end 116 117 def prefix 118 name = dig(:prefix) 119 Panda.prefixes_hash[name] if name 120 end 121 122 # Suggested handler name 123 def handler_name 124 opcode.upcase.gsub(/_PROF\d+/, '') 125 end 126 127 def intrinsic_name 128 dig(:intrinsic_name) 129 end 130 131 def compilable? 132 properties.empty? || (!properties.include? 'not_compilable') 133 end 134 135 def inlinable? 136 properties.include? 'inlinable' 137 end 138 139 def profiled? 140 dig(:profile) 141 end 142 143 def opcode_idx 144 if prefix 145 dig(:opcode_idx) << 8 | prefix.opcode_idx 146 else 147 dig(:opcode_idx) 148 end 149 end 150 151 # Format instance for raw-data format name 152 def format 153 Panda.format_hash[dig(:format)] || Quick.format_hash[dig(:format)] 154 end 155 156 # Array of explicit operands 157 cached def operands 158 return [] unless sig.include? ' ' 159 160 _, operands = sig.match(/(\S+) (.+)/).captures 161 operands = operands.split(', ') 162 ops_encoding = format.encoding 163 164 operands.map do |operand| 165 name, srcdst, type = Util.parse_operand_signature(operand) 166 key = name 167 key = 'id' if name.end_with?('id') 168 Operand.new(name, srcdst, type, ops_encoding[key].width, ops_encoding[key].offset) 169 end 170 end 171 172 # Used by compiler 173 # Operands array preceeded with accumulator as if it was a regular operand 174 # Registers that are both destination and source are uncoupled 175 cached def acc_and_operands 176 res = Util.parse_acc_signature(acc) 177 operands.each_with_object(res) do |op, ops| 178 if op.dst? && op.src? 179 ops << Operand.new(op.name, 'out', op.type, op.width, op.offset) 180 ops << Operand.new(op.name, 'in', op.type, op.width, op.offset) 181 else 182 ops << op 183 end 184 end 185 end 186 187 cached def properties 188 props = dig(:properties) || [] 189 # Added for back compatibility: 190 add_props = [] 191 add_props << 'acc_write' if acc_write? 192 add_props << 'acc_read' if acc_read? 193 add_props << 'acc_none' if acc_none? 194 props + add_props 195 end 196 197 def type(index) 198 acc_and_operands.select(&:src?)[index].type || 'none' 199 end 200 201 # Type of single destination operand ("none" if there are no such) 202 def dtype 203 acc_and_operands.select(&:dst?).first&.type || 'none' 204 end 205 206 # Shortcut for querying 'float' property 207 def float? 208 properties.include? 'float' 209 end 210 211 # Shortcut for querying 'jump' property 212 def jump? 213 properties.include? 'jump' 214 end 215 216 # Shortcut for querying 'conditional' property 217 def conditional? 218 properties.include? 'conditional' 219 end 220 221 # Shortcut for querying 'x_none' exception 222 def throwing? 223 !exceptions.include? 'x_none' 224 end 225 226 def acc_read? 227 !acc_and_operands.select(&:acc?).select(&:src?).empty? 228 end 229 230 def acc_write? 231 !acc_and_operands.select(&:acc?).select(&:dst?).empty? 232 end 233 234 def acc_none? 235 acc_and_operands.select(&:acc?).empty? 236 end 237 238 def namespace 239 dig(:namespace) || 'core' 240 end 241 242 cached def profile 243 Panda.profiles[dig(:profile)] 244 end 245 246 include FreezeMixin 247 freeze_defined_methods 248end 249 250class Prefix < SimpleDelegator 251 # Suggested handler name 252 def handler_name 253 name.upcase 254 end 255end 256 257# Dummy class for invalid handlers 258class Invalid 259 def handler_name 260 'INVALID' 261 end 262end 263 264# Methods over format names 265# 266class Format 267 attr_reader :name 268 269 def initialize(name) 270 @name = name 271 end 272 273 cached def pretty 274 name.sub('op_', '').gsub(/imm[0-9]?/, 'imm').gsub(/v[0-9]?/, 'v').gsub(/_([0-9]+)/, '\1') 275 end 276 277 def prefixed? 278 name.start_with?('pref_') 279 end 280 281 cached def size 282 bits = pretty.gsub(/[a-z]/, '').split('_').map(&:to_i).sum 283 raise "Incorrect format name #{name}" if bits % 8 != 0 284 285 opcode_bytes = prefixed? ? 2 : 1 286 bits / 8 + opcode_bytes 287 end 288 289 cached def encoding 290 return {} if name.end_with?('_none') 291 292 offset = prefixed? ? 16 : 8 293 encoding = {} 294 encoding.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 295 name.sub('pref_', '').sub('op_', '').sub('none_', '').split('_').each_slice(2).map do |name, width| 296 op = OpenStruct.new 297 op.name = name 298 op.width = width.to_i 299 op.offset = offset 300 offset += op.width 301 encoding[name] = op 302 end 303 encoding 304 end 305 306 # Return [offset, width] for profile id element 307 def profile_info 308 raise "Format #{name} has no profile" unless profiled? 309 310 prof = encoding.values.select { |x| x.name == 'prof' } 311 raise "Profile info not found in format #{name}" if prof.size != 1 312 313 [prof[0].offset, prof[0].width] 314 end 315 316 def profiled? 317 name.include? 'prof' 318 end 319 320 include FreezeMixin 321 freeze_defined_methods 322end 323 324# Operand types and encoding 325# 326class Operand 327 attr_reader :name, :type, :offset, :width 328 329 def initialize(name, srcdst, type, width = 0, offset = 0) 330 @name = name.to_s.gsub(/[0-9]/, '').to_sym 331 unless %i[v acc imm method_id type_id field_id string_id literalarray_id prof].include?(@name) 332 raise "Incorrect operand #{name}" 333 end 334 335 @srcdst = srcdst.to_sym || :in 336 types = %i[none u1 u2 i8 u8 i16 u16 i32 u32 b32 f32 i64 u64 b64 f64 ref top any] 337 raise "Incorrect type #{type}" unless types.include?(type.sub('[]', '').to_sym) 338 339 @type = type 340 @width = width 341 @offset = offset 342 end 343 344 def reg? 345 @name == :v 346 end 347 348 def acc? 349 @name == :acc 350 end 351 352 def imm? 353 @name == :imm 354 end 355 356 def id? 357 %i[method_id type_id field_id string_id literalarray_id].include?(@name) 358 end 359 360 def prof? 361 @name == :prof 362 end 363 364 def dst? 365 %i[inout out].include?(@srcdst) 366 end 367 368 def src? 369 %i[inout in].include?(@srcdst) 370 end 371 372 def size 373 @type[1..-1].to_i 374 end 375 376 include FreezeMixin 377 freeze_defined_methods 378end 379 380# Helper class for generating dispatch tables 381class DispatchTable 382 # Canonical order of dispatch table consisting of 383 # * non-prefixed instructions handlers 384 # * invalid handlers 385 # * prefix handlers that re-dispatch to prefixed instruction based on second byte of opcode_idx 386 # * prefixed instructions handlers, in the order of prefixes 387 # Return array with proposed handler names 388 def handler_names 389 handlers = Panda.instructions.reject(&:prefix) + 390 Array.new(invalid_non_prefixed_interval.size, Invalid.new) + 391 Panda.prefixes.select(&:public?) + 392 Array.new(invalid_prefixes_interval.size, Invalid.new) + 393 Panda.prefixes.reject(&:public?) + 394 Panda.instructions.select(&:prefix).stable_sort_by { |i| Panda.prefixes_hash[i.prefix.name].opcode_idx } 395 396 handlers.map(&:handler_name) 397 end 398 399 def invalid_non_prefixed_interval 400 (Panda.instructions.reject(&:prefix).map(&:opcode_idx).max + 1)..(Panda.prefixes.map(&:opcode_idx).min - 1) 401 end 402 403 def invalid_prefixes_interval 404 max_invalid_idx = Panda.prefixes.reject(&:public?).map(&:opcode_idx).min || 256 405 (Panda.prefixes.select(&:public?).map(&:opcode_idx).max + 1)..(max_invalid_idx - 1) 406 end 407 408 # Maximum value for secondary dispatch index for given prefix name 409 def secondary_opcode_bound(prefix) 410 prefix_data[prefix.name][:number_of_insns] - 1 411 end 412 413 # Offset in dispatch table for handlers of instructions for given prefix name 414 def secondary_opcode_offset(prefix) 415 256 + prefix_data[prefix.name][:delta] 416 end 417 418 private 419 420 cached def prefix_data 421 cur_delta = 0 422 Panda.prefixes.each_with_object({}) do |p, obj| 423 prefix_instructions_num = Panda.instructions.select { |i| i.prefix && (i.prefix.name == p.name) }.size 424 obj[p.name] = { delta: cur_delta, number_of_insns: prefix_instructions_num } 425 cur_delta += prefix_instructions_num 426 end 427 end 428end 429 430# Auxilary classes for opcode assignment 431class OpcodeAssigner 432 def initialize 433 @table = Hash.new { |h, k| h[k] = Set.new } 434 @all_opcodes = Set.new(0..255) 435 end 436 437 def consume(item) 438 raise 'Cannot consume instruction without opcode' unless item.opcode_idx 439 440 @table[prefix(item)] << item.opcode_idx 441 end 442 443 def yield_opcode(item) 444 return item.opcode_idx if item.opcode_idx 445 446 opcodes = @table[prefix(item)] 447 choose_opcode(opcodes) 448 end 449 450 private 451 452 def choose_opcode(occupied_opcodes) 453 (@all_opcodes - occupied_opcodes).min 454 end 455 456 def prefix(item) 457 item.prefix || 'non_prefixed' 458 end 459end 460 461class PrefixOpcodeAssigner < OpcodeAssigner 462 private 463 464 # override opcodes assignment for prefixes 465 def choose_opcode(occupied_opcodes) 466 (@all_opcodes - occupied_opcodes).max 467 end 468end 469 470# A bunch of handy methods for template generating 471# 472# All yaml properties are accessible by '.' syntax, 473# e.g. 'Panda::groups[0].instruction[0].format' 474# 475module Panda 476 module_function 477 478 def properties 479 @data.properties + 480 [OpenStruct.new(tag: 'acc_none', description: 'Doesn\'t use accumulator register.'), 481 OpenStruct.new(tag: 'acc_read', description: 'Use accumulator as a first source operand.'), 482 OpenStruct.new(tag: 'acc_write', description: 'Use accumulator as a destination operand.')] 483 end 484 485 def verify_schema(name, data, schema) 486 data.each do |item| 487 item.each_pair do |key, value| 488 element = schema[key] 489 raise "Schema verification failed for #{name}: no element in schema: #{key}" unless element 490 491 case element 492 when 'string' 493 raise "Schema verification failed for #{name}: #{key} must be a string" unless value.is_a? String 494 when 'int' 495 raise "Schema verification failed for #{name}: #{key} must be an integer" unless value.is_a? Integer 496 when Array 497 value.each do |x| 498 raise "Schema verification failed for #{name}: unexpected array value: #{x}" unless element.include? x 499 end 500 end 501 end 502 end 503 end 504 505 cached def profiles 506 verify_schema('Profiles', @data.profiles, @data.profiles_schema) 507 @data.profiles.map { |x| [x.name, x] }.to_h 508 end 509 510 def quickened_plugins 511 @data.namespaces.map { |i| [i.namespace, i.used_instructions] if i.quickening }.compact.to_h 512 end 513 514 # Hash with exception tag as a key and exception description as a value 515 cached def exceptions_hash 516 convert_to_hash(exceptions) 517 end 518 519 # Hash with property tag as a key and property description as a value 520 cached def properties_hash 521 convert_to_hash(properties) 522 end 523 524 # Hash with verification tag as a key and verification description as a value 525 cached def verification_hash 526 convert_to_hash(verification) 527 end 528 529 cached def prefixes_hash 530 hash = prefixes.map { |p| [p.name, p] }.to_h 531 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 532 hash 533 end 534 535 # Hash from format names to Format instances 536 cached def format_hash 537 each_data_instruction.with_object([]) do |instruction, fmts| 538 fmt_name = instruction.format 539 fmts << [fmt_name, Format.new(fmt_name)] 540 end.to_h 541 end 542 543 # Array of Instruction instances for every possible instruction 544 cached def instructions 545 opcodes = OpcodeAssigner.new 546 tmp_public = initialize_instructions(opcodes, &:opcode_idx) 547 tmp_private = initialize_instructions(opcodes) { |ins| !ins.opcode_idx } 548 tmp = tmp_public + tmp_private 549 @instructions = tmp.sort_by(&:opcode_idx) 550 end 551 552 cached def prefixes 553 opcodes = PrefixOpcodeAssigner.new 554 tmp_public = initialize_prefixes(opcodes, &:opcode_idx) 555 tmp_private = initialize_prefixes(opcodes) { |p| !p.opcode_idx } 556 tmp = tmp_public + tmp_private 557 @prefixes = tmp.sort_by(&:opcode_idx) 558 end 559 560 def dispatch_table 561 DispatchTable.new 562 end 563 564 # Array of all Format instances 565 def formats 566 format_hash.merge(Quick.format_hash).values.uniq(&:pretty).sort_by(&:pretty) 567 end 568 569 # delegating part of module 570 # 571 def wrap_data(data) 572 @data = data 573 end 574 575 def respond_to_missing?(method_name, include_private = false) 576 @data.respond_to?(method_name, include_private) 577 end 578 579 def method_missing(method, *args, &block) 580 if respond_to_missing? method 581 @data.send(method, *args, &block) 582 else 583 super 584 end 585 end 586 587 cached def each_data_instruction 588 # create separate instance for every instruction format and inherit group properties 589 groups.each_with_object([]) do |g, obj| 590 g.instructions.each do |i| 591 data_insn = merge_group_and_insn(g, i) 592 if data_insn[:opcode_idx] && (data_insn[:opcode_idx].size != data_insn[:format].size) 593 raise 'format and opcode_idx arrays should have equal size' 594 end 595 596 data_insn[:format].each_with_index do |f, idx| 597 insn = data_insn.dup 598 insn[:format] = f 599 insn[:opcode_idx] = data_insn[:opcode_idx][idx] if data_insn[:opcode_idx] 600 obj << OpenStruct.new(insn) 601 end 602 end 603 end.to_enum 604 end 605 606 # private functions 607 # 608 private_class_method def convert_to_hash(arr) 609 hash = arr.map { |i| [i.tag, i.description] }.to_h 610 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 611 hash 612 end 613 614 private_class_method def merge_group_and_insn(group, insn) 615 props = group.to_h 616 props.delete(:instructions) 617 props.merge(insn.to_h) do |_, old, new| 618 if old.is_a?(Array) && new.is_a?(Array) 619 old | new # extend array-like properties instead of overriding 620 else 621 new 622 end 623 end 624 end 625 626 private_class_method def initialize_instructions(opcodes, &block) 627 each_data_instruction.select(&block).each_with_object([]) do |instruction, insns| 628 insn = instruction.clone 629 insn[:public?] = !insn.opcode_idx.nil? 630 insn.opcode_idx = opcodes.yield_opcode(insn) 631 opcodes.consume(insn) 632 insns << Instruction.new(insn) 633 end 634 end 635 636 private_class_method def initialize_prefixes(opcodes, &block) 637 dig(:prefixes).select(&block).each_with_object([]) do |pref, res| 638 p = pref.clone 639 p[:public?] = !p.opcode_idx.nil? 640 p.opcode_idx = opcodes.yield_opcode(p) 641 opcodes.consume(p) 642 res << Prefix.new(p) 643 end 644 end 645 646 def Gen.on_require(data) 647 Panda.wrap_data(data) 648 Quick.init 649 end 650end 651 652module Quick 653 module_function 654 655 def init 656 @format_hash = {} 657 @select = Hash.new { |h, k| h[k] = [] } 658 Panda.each_data_instruction.each do |insn| 659 add_to_quick(OpenStruct.new(insn)) if !insn.namespace || Panda.quickened_plugins[insn.namespace] 660 end 661 end 662 663 def add_to_quick(insn) 664 if insn.namespace 665 insn.format = remove_pref(insn.format) 666 insn.prefix = '' 667 end 668 ins = Instruction.new(insn) 669 @format_hash[insn.format] = Format.new(insn.format) 670 if ins.namespace == 'core' 671 Panda.quickened_plugins.each do |ns, used| 672 @select[ns].push(ins.clone) if used.include?(ins.mnemonic) 673 end 674 else 675 raise "Plugin #{ins.namespace} is not quickened" unless Panda.quickened_plugins[insn.namespace] 676 677 @select[ins.namespace].push(ins) 678 end 679 end 680 681 def format_hash 682 @format_hash 683 end 684 685 def instructions 686 arr = [] 687 @select.each do |_, insns| 688 arr.concat(insns) 689 end 690 arr.uniq(&:opcode) 691 end 692 693 def select 694 @select 695 end 696 697 def formats 698 @format_hash.values.uniq(&:pretty).sort_by(&:pretty) 699 end 700 701 def remove_pref(str) 702 str.sub('_PREF_NONE', '').sub('_pref_none', '').sub('PREF_', '').sub('pref_', '') 703 end 704end 705