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 is_64bit_imm? 349 %i[i64 f64 b64 u64].include?(@type.to_sym) 350 end 351 352 def acc? 353 @name == :acc 354 end 355 356 def imm? 357 @name == :imm 358 end 359 360 def id? 361 %i[method_id type_id field_id string_id literalarray_id].include?(@name) 362 end 363 364 def prof? 365 @name == :prof 366 end 367 368 def dst? 369 %i[inout out].include?(@srcdst) 370 end 371 372 def src? 373 %i[inout in].include?(@srcdst) 374 end 375 376 def size 377 @type[1..-1].to_i 378 end 379 380 include FreezeMixin 381 freeze_defined_methods 382end 383 384# Helper class for generating dispatch tables 385class DispatchTable 386 # Canonical order of dispatch table consisting of 387 # * non-prefixed instructions handlers 388 # * invalid handlers 389 # * prefix handlers that re-dispatch to prefixed instruction based on second byte of opcode_idx 390 # * prefixed instructions handlers, in the order of prefixes 391 # Return array with proposed handler names 392 def handler_names 393 handlers = Panda.instructions.reject(&:prefix) + 394 Array.new(invalid_non_prefixed_interval.size, Invalid.new) + 395 Panda.prefixes.select(&:public?) + 396 Array.new(invalid_prefixes_interval.size, Invalid.new) + 397 Panda.prefixes.reject(&:public?) + 398 Panda.instructions.select(&:prefix).stable_sort_by { |i| Panda.prefixes_hash[i.prefix.name].opcode_idx } 399 400 handlers.map(&:handler_name) 401 end 402 403 def invalid_non_prefixed_interval 404 (Panda.instructions.reject(&:prefix).map(&:opcode_idx).max + 1)..(Panda.prefixes.map(&:opcode_idx).min - 1) 405 end 406 407 def invalid_prefixes_interval 408 max_invalid_idx = Panda.prefixes.reject(&:public?).map(&:opcode_idx).min || 256 409 (Panda.prefixes.select(&:public?).map(&:opcode_idx).max + 1)..(max_invalid_idx - 1) 410 end 411 412 # Maximum value for secondary dispatch index for given prefix name 413 def secondary_opcode_bound(prefix) 414 prefix_data[prefix.name][:number_of_insns] - 1 415 end 416 417 # Offset in dispatch table for handlers of instructions for given prefix name 418 def secondary_opcode_offset(prefix) 419 256 + prefix_data[prefix.name][:delta] 420 end 421 422 private 423 424 cached def prefix_data 425 cur_delta = 0 426 Panda.prefixes.each_with_object({}) do |p, obj| 427 prefix_instructions_num = Panda.instructions.select { |i| i.prefix && (i.prefix.name == p.name) }.size 428 obj[p.name] = { delta: cur_delta, number_of_insns: prefix_instructions_num } 429 cur_delta += prefix_instructions_num 430 end 431 end 432end 433 434# Auxilary classes for opcode assignment 435class OpcodeAssigner 436 def initialize 437 @table = Hash.new { |h, k| h[k] = Set.new } 438 @all_opcodes = Set.new(0..255) 439 end 440 441 def consume(item) 442 raise 'Cannot consume instruction without opcode' unless item.opcode_idx 443 444 @table[prefix(item)] << item.opcode_idx 445 end 446 447 def yield_opcode(item) 448 return item.opcode_idx if item.opcode_idx 449 450 opcodes = @table[prefix(item)] 451 choose_opcode(opcodes) 452 end 453 454 private 455 456 def choose_opcode(occupied_opcodes) 457 (@all_opcodes - occupied_opcodes).min 458 end 459 460 def prefix(item) 461 item.prefix || 'non_prefixed' 462 end 463end 464 465class PrefixOpcodeAssigner < OpcodeAssigner 466 private 467 468 # override opcodes assignment for prefixes 469 def choose_opcode(occupied_opcodes) 470 (@all_opcodes - occupied_opcodes).max 471 end 472end 473 474# A bunch of handy methods for template generating 475# 476# All yaml properties are accessible by '.' syntax, 477# e.g. 'Panda::groups[0].instruction[0].format' 478# 479module Panda 480 module_function 481 482 def properties 483 @data.properties + 484 [OpenStruct.new(tag: 'acc_none', description: 'Doesn\'t use accumulator register.'), 485 OpenStruct.new(tag: 'acc_read', description: 'Use accumulator as a first source operand.'), 486 OpenStruct.new(tag: 'acc_write', description: 'Use accumulator as a destination operand.')] 487 end 488 489 def verify_schema(name, data, schema) 490 data.each do |item| 491 item.each_pair do |key, value| 492 element = schema[key] 493 raise "Schema verification failed for #{name}: no element in schema: #{key}" unless element 494 495 case element 496 when 'string' 497 raise "Schema verification failed for #{name}: #{key} must be a string" unless value.is_a? String 498 when 'int' 499 raise "Schema verification failed for #{name}: #{key} must be an integer" unless value.is_a? Integer 500 when Array 501 value.each do |x| 502 raise "Schema verification failed for #{name}: unexpected array value: #{x}" unless element.include? x 503 end 504 end 505 end 506 end 507 end 508 509 cached def profiles 510 verify_schema('Profiles', @data.profiles, @data.profiles_schema) 511 @data.profiles.map { |x| [x.name, x] }.to_h 512 end 513 514 def quickened_plugins 515 @data.namespaces.map { |i| [i.namespace, i.used_instructions] if i.quickening }.compact.to_h 516 end 517 518 # Hash with exception tag as a key and exception description as a value 519 cached def exceptions_hash 520 convert_to_hash(exceptions) 521 end 522 523 # Hash with property tag as a key and property description as a value 524 cached def properties_hash 525 convert_to_hash(properties) 526 end 527 528 # Hash with verification tag as a key and verification description as a value 529 cached def verification_hash 530 convert_to_hash(verification) 531 end 532 533 cached def prefixes_hash 534 hash = prefixes.map { |p| [p.name, p] }.to_h 535 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 536 hash 537 end 538 539 # Hash from format names to Format instances 540 cached def format_hash 541 each_data_instruction.with_object([]) do |instruction, fmts| 542 fmt_name = instruction.format 543 fmts << [fmt_name, Format.new(fmt_name)] 544 end.to_h 545 end 546 547 # Array of Instruction instances for every possible instruction 548 cached def instructions 549 opcodes = OpcodeAssigner.new 550 tmp_public = initialize_instructions(opcodes, &:opcode_idx) 551 tmp_private = initialize_instructions(opcodes) { |ins| !ins.opcode_idx } 552 tmp = tmp_public + tmp_private 553 @instructions = tmp.sort_by(&:opcode_idx) 554 end 555 556 cached def prefixes 557 opcodes = PrefixOpcodeAssigner.new 558 tmp_public = initialize_prefixes(opcodes, &:opcode_idx) 559 tmp_private = initialize_prefixes(opcodes) { |p| !p.opcode_idx } 560 tmp = tmp_public + tmp_private 561 @prefixes = tmp.sort_by(&:opcode_idx) 562 end 563 564 def dispatch_table 565 DispatchTable.new 566 end 567 568 # Array of all Format instances 569 def formats 570 format_hash.merge(Quick.format_hash).values.uniq(&:pretty).sort_by(&:pretty) 571 end 572 573 # delegating part of module 574 # 575 def wrap_data(data) 576 @data = data 577 end 578 579 def respond_to_missing?(method_name, include_private = false) 580 @data.respond_to?(method_name, include_private) 581 end 582 583 def method_missing(method, *args, &block) 584 if respond_to_missing? method 585 @data.send(method, *args, &block) 586 else 587 super 588 end 589 end 590 591 cached def each_data_instruction 592 # create separate instance for every instruction format and inherit group properties 593 groups.each_with_object([]) do |g, obj| 594 g.instructions.each do |i| 595 data_insn = merge_group_and_insn(g, i) 596 if data_insn[:opcode_idx] && (data_insn[:opcode_idx].size != data_insn[:format].size) 597 raise 'format and opcode_idx arrays should have equal size' 598 end 599 600 data_insn[:format].each_with_index do |f, idx| 601 insn = data_insn.dup 602 insn[:format] = f 603 insn[:opcode_idx] = data_insn[:opcode_idx][idx] if data_insn[:opcode_idx] 604 obj << OpenStruct.new(insn) 605 end 606 end 607 end.to_enum 608 end 609 610 # private functions 611 # 612 private_class_method def convert_to_hash(arr) 613 hash = arr.map { |i| [i.tag, i.description] }.to_h 614 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 615 hash 616 end 617 618 private_class_method def merge_group_and_insn(group, insn) 619 props = group.to_h 620 props.delete(:instructions) 621 props.merge(insn.to_h) do |_, old, new| 622 if old.is_a?(Array) && new.is_a?(Array) 623 old | new # extend array-like properties instead of overriding 624 else 625 new 626 end 627 end 628 end 629 630 private_class_method def initialize_instructions(opcodes, &block) 631 each_data_instruction.select(&block).each_with_object([]) do |instruction, insns| 632 insn = instruction.clone 633 insn[:public?] = !insn.opcode_idx.nil? 634 insn.opcode_idx = opcodes.yield_opcode(insn) 635 opcodes.consume(insn) 636 insns << Instruction.new(insn) 637 end 638 end 639 640 private_class_method def initialize_prefixes(opcodes, &block) 641 dig(:prefixes).select(&block).each_with_object([]) do |pref, res| 642 p = pref.clone 643 p[:public?] = !p.opcode_idx.nil? 644 p.opcode_idx = opcodes.yield_opcode(p) 645 opcodes.consume(p) 646 res << Prefix.new(p) 647 end 648 end 649 650 def Gen.on_require(data) 651 Panda.wrap_data(data) 652 Quick.init 653 end 654end 655 656module Quick 657 module_function 658 659 def init 660 @format_hash = {} 661 @select = Hash.new { |h, k| h[k] = [] } 662 Panda.each_data_instruction.each do |insn| 663 add_to_quick(OpenStruct.new(insn)) if !insn.namespace || Panda.quickened_plugins[insn.namespace] 664 end 665 end 666 667 def add_to_quick(insn) 668 if insn.namespace 669 insn.format = remove_pref(insn.format) 670 insn.prefix = '' 671 end 672 ins = Instruction.new(insn) 673 @format_hash[insn.format] = Format.new(insn.format) 674 if ins.namespace == 'core' 675 Panda.quickened_plugins.each do |ns, used| 676 @select[ns].push(ins.clone) if used.include?(ins.mnemonic) 677 end 678 else 679 raise "Plugin #{ins.namespace} is not quickened" unless Panda.quickened_plugins[insn.namespace] 680 681 @select[ins.namespace].push(ins) 682 end 683 end 684 685 def format_hash 686 @format_hash 687 end 688 689 def instructions 690 arr = [] 691 @select.each do |_, insns| 692 arr.concat(insns) 693 end 694 arr.uniq(&:opcode) 695 end 696 697 def select 698 @select 699 end 700 701 def formats 702 @format_hash.values.uniq(&:pretty).sort_by(&:pretty) 703 end 704 705 def remove_pref(str) 706 str.sub('_PREF_NONE', '').sub('_pref_none', '').sub('PREF_', '').sub('pref_', '') 707 end 708end 709