1# Copyright (c) 2021-2022 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 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 no_side_effect? 140 properties.include? 'no_side_effect' 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)] 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 count = 0 165 operands.map do |operand| 166 name, srcdst, type = Util.parse_operand_signature(operand) 167 if name.end_with?('id') 168 count += 1 169 end 170 end 171 172 id_count = 1 173 operands.map do |operand| 174 name, srcdst, type = Util.parse_operand_signature(operand) 175 key = name 176 if name.end_with?('id') 177 key = 'id' 178 if count > 1 179 key += id_count.to_s 180 id_count = id_count + 1 181 end 182 end 183 Operand.new(name, srcdst, type, ops_encoding[key].width, ops_encoding[key].offset) 184 end 185 end 186 187 # Used by compiler 188 # Operands array preceeded with accumulator as if it was a regular operand 189 # Registers that are both destination and source are uncoupled 190 cached def acc_and_operands 191 res = Util.parse_acc_signature(acc) 192 operands.each_with_object(res) do |op, ops| 193 if op.dst? && op.src? 194 ops << Operand.new(op.name, 'out', op.type, op.width, op.offset) 195 ops << Operand.new(op.name, 'in', op.type, op.width, op.offset) 196 else 197 ops << op 198 end 199 end 200 end 201 202 cached def properties 203 props = dig(:properties) || [] 204 # Added for back compatibility: 205 add_props = [] 206 add_props << 'acc_write' if acc_write? 207 add_props << 'acc_read' if acc_read? 208 add_props << 'acc_none' if acc_none? 209 props + add_props 210 end 211 212 cached def real_properties 213 filter = [] 214 properties.each do |p| 215 if p != 'acc_write' && p != 'acc_read' && p != 'acc_none' 216 filter << p 217 end 218 end 219 filter << 'acc_write' if acc_write? 220 filter << 'acc_read' if acc_read? 221 filter << 'acc_none' if acc_none? 222 return filter 223 end 224 225 226 def type(index) 227 acc_and_operands.select(&:src?)[index].type || 'none' 228 end 229 230 # Type of single destination operand ("none" if there are no such) 231 def dtype 232 acc_and_operands.select(&:dst?).first&.type || 'none' 233 end 234 235 # Shortcut for querying 'float' property 236 def float? 237 properties.include? 'float' 238 end 239 240 # Shortcut for querying 'jump' property 241 def jump? 242 properties.include? 'jump' 243 end 244 245 # Shortcut for querying 'conditional' property 246 def conditional? 247 properties.include? 'conditional' 248 end 249 250 # Shortcut for querying 'x_none' exception 251 def throwing? 252 !exceptions.include? 'x_none' 253 end 254 255 def acc_read? 256 !acc_and_operands.select(&:acc?).select(&:src?).empty? 257 end 258 259 def acc_write? 260 !acc_and_operands.select(&:acc?).select(&:dst?).empty? 261 end 262 263 def acc_none? 264 acc_and_operands.select(&:acc?).empty? 265 end 266 267 def namespace 268 dig(:namespace) || 'core' 269 end 270 271 def is_range_0? 272 properties.include?('range_0') 273 end 274 275 def is_range_1? 276 properties.include?('range_1') 277 end 278 279 def is_range_instruction? 280 is_range_0? || is_range_1? 281 end 282 283 def is_return_instruction? 284 properties.include?('return') 285 end 286 287 def is_unconditional_throw_instruction? 288 dig(:prefix) == 'throw' && !properties.include?('conditional_throw') 289 end 290 291 include FreezeMixin 292 freeze_defined_methods 293end 294 295class Prefix < SimpleDelegator 296 # Suggested handler name 297 def handler_name 298 name.upcase 299 end 300end 301 302# Dummy class for invalid handlers 303class Invalid 304 def handler_name 305 'INVALID' 306 end 307end 308 309# Methods over format names 310# 311class Format 312 attr_reader :name 313 314 def initialize(name) 315 @name = name 316 end 317 318 cached def pretty 319 name.sub('op_', '').gsub(/id[0-9]?/, 'id').gsub(/imm[0-9]?/, 'imm').gsub(/v[0-9]?/, 'v').gsub(/_([0-9]+)/, '\1') 320 end 321 322 def prefixed? 323 name.start_with?('pref_') 324 end 325 326 cached def size 327 bits = pretty.gsub(/[a-z]/, '').split('_').map(&:to_i).sum 328 raise "Incorrect format name #{name}" if bits % 8 != 0 329 330 opcode_bytes = prefixed? ? 2 : 1 331 bits / 8 + opcode_bytes 332 end 333 334 cached def encoding 335 return {} if name.end_with?('_none') 336 337 offset = prefixed? ? 16 : 8 338 encoding = {} 339 encoding.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 340 name.sub('pref_', '').sub('op_', '').split('_').each_slice(2).map do |name, width| 341 op = OpenStruct.new 342 op.name = name 343 op.width = width.to_i 344 op.offset = offset 345 offset += op.width 346 encoding[name] = op 347 end 348 encoding 349 end 350 351 include FreezeMixin 352 freeze_defined_methods 353end 354 355# Operand types and encoding 356# 357class Operand 358 attr_reader :name, :type, :offset, :width 359 360 def initialize(name, srcdst, type, width = 0, offset = 0) 361 @name = name.to_s.gsub(/[0-9]/, '').to_sym 362 unless %i[v acc imm method_id type_id field_id string_id literalarray_id].include?(@name) 363 raise "Incorrect operand #{name}" 364 end 365 366 @srcdst = srcdst.to_sym || :in 367 types = %i[none u1 u2 i8 u8 i16 u16 i32 u32 b32 f32 i64 u64 b64 f64 ref top any] 368 raise "Incorrect type #{type}" unless types.include?(type.sub('[]', '').to_sym) 369 370 @type = type 371 @width = width 372 @offset = offset 373 end 374 375 def reg? 376 @name == :v 377 end 378 379 def acc? 380 @name == :acc 381 end 382 383 def imm? 384 @name == :imm 385 end 386 387 def is_float_imm? 388 %i[f32 f64].include?(@type.to_sym) 389 end 390 391 def is_signed_imm? 392 %i[i8 i16 i32 i64].include?(@type.to_sym) 393 end 394 395 def is_unsigned_imm? 396 %i[u1 u2 u8 u16 u32 u64].include?(@type.to_sym) 397 end 398 399 def id? 400 %i[method_id type_id field_id string_id literalarray_id].include?(@name) 401 end 402 403 def method_id? 404 %i[method_id].include?(@name) 405 end 406 407 def string_id? 408 %i[string_id].include?(@name) 409 end 410 411 def literalarray_id? 412 %i[literalarray_id].include?(@name) 413 end 414 415 def dst? 416 %i[inout out].include?(@srcdst) 417 end 418 419 def src? 420 %i[inout in].include?(@srcdst) 421 end 422 423 def size 424 @type[1..-1].to_i 425 end 426 427 include FreezeMixin 428 freeze_defined_methods 429end 430 431# Helper class for generating dispatch tables 432class DispatchTable 433 # Canonical order of dispatch table consisting of 434 # * non-prefixed instructions handlers 435 # * invalid handlers 436 # * prefix handlers that re-dispatch to prefixed instruction based on second byte of opcode_idx 437 # * prefixed instructions handlers, in the order of prefixes 438 # Return array with proposed handler names 439 def handler_names 440 handlers = Panda.instructions.reject(&:prefix) + 441 Array.new(invalid_non_prefixed_interval.size, Invalid.new) + 442 Panda.prefixes.select(&:public?) + 443 Array.new(invalid_prefixes_interval.size, Invalid.new) + 444 Panda.prefixes.reject(&:public?) + 445 Panda.instructions.select(&:prefix).stable_sort_by { |i| Panda.prefixes_hash[i.prefix.name].opcode_idx } 446 447 handlers.map(&:handler_name) 448 end 449 450 def invalid_non_prefixed_interval 451 (Panda.instructions.reject(&:prefix).map(&:opcode_idx).max + 1)..(Panda.prefixes.map(&:opcode_idx).min - 1) 452 end 453 454 def invalid_prefixes_interval 455 max_invalid_idx = Panda.prefixes.reject(&:public?).map(&:opcode_idx).min || 256 456 (Panda.prefixes.select(&:public?).map(&:opcode_idx).max + 1)..(max_invalid_idx - 1) 457 end 458 459 # Maximum value for secondary dispatch index for given prefix name 460 def secondary_opcode_bound(prefix) 461 prefix_data[prefix.name][:number_of_insns] - 1 462 end 463 464 # Offset in dispatch table for handlers of instructions for given prefix name 465 def secondary_opcode_offset(prefix) 466 256 + prefix_data[prefix.name][:delta] 467 end 468 469 private 470 471 cached def prefix_data 472 cur_delta = 0 473 Panda.prefixes.each_with_object({}) do |p, obj| 474 prefix_instructions_num = Panda.instructions.select { |i| i.prefix && (i.prefix.name == p.name) }.size 475 obj[p.name] = { delta: cur_delta, number_of_insns: prefix_instructions_num } 476 cur_delta += prefix_instructions_num 477 end 478 end 479end 480 481# Auxilary classes for opcode assignment 482class OpcodeAssigner 483 def initialize 484 @table = Hash.new { |h, k| h[k] = Set.new } 485 @all_opcodes = Set.new(0..255) 486 end 487 488 def consume(item) 489 raise 'Cannot consume instruction without opcode' unless item.opcode_idx 490 491 @table[prefix(item)] << item.opcode_idx 492 end 493 494 def yield_opcode(item) 495 return item.opcode_idx if item.opcode_idx 496 497 opcodes = @table[prefix(item)] 498 choose_opcode(opcodes) 499 end 500 501 private 502 503 def choose_opcode(occupied_opcodes) 504 (@all_opcodes - occupied_opcodes).min 505 end 506 507 def prefix(item) 508 item.prefix.nil? ? 'non_prefixed' : item.prefix 509 end 510end 511 512class PrefixOpcodeAssigner < OpcodeAssigner 513 private 514 515 # override opcodes assignment for prefixes 516 def choose_opcode(occupied_opcodes) 517 (@all_opcodes - occupied_opcodes).max 518 end 519end 520 521# A bunch of handy methods for template generating 522# 523# All yaml properties are accessible by '.' syntax, 524# e.g. 'Panda::groups[0].instruction[0].format' 525# 526module Panda 527 module_function 528 529 def properties 530 @data.properties + 531 [OpenStruct.new(tag: 'acc_none', description: 'Doesn\'t use accumulator register.'), 532 OpenStruct.new(tag: 'acc_read', description: 'Use accumulator as a first source operand.'), 533 OpenStruct.new(tag: 'acc_write', description: 'Use accumulator as a destination operand.')] 534 end 535 536 # Hash with exception tag as a key and exception description as a value 537 cached def exceptions_hash 538 convert_to_hash(exceptions) 539 end 540 541 # Hash with property tag as a key and property description as a value 542 cached def properties_hash 543 convert_to_hash(properties) 544 end 545 546 # Hash with verification tag as a key and verification description as a value 547 cached def verification_hash 548 convert_to_hash(verification) 549 end 550 551 cached def prefixes_hash 552 hash = prefixes.map { |p| [p.name, p] }.to_h 553 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 554 hash 555 end 556 557 # Hash from format names to Format instances 558 cached def format_hash 559 each_data_instruction.with_object([]) do |instruction, fmts| 560 fmt_name = instruction.format 561 fmts << [fmt_name, Format.new(fmt_name)] 562 end.to_h 563 end 564 565 # Array of Instruction instances for every possible instruction 566 cached def instructions 567 opcodes = OpcodeAssigner.new 568 tmp_public = initialize_instructions(opcodes) { |ins| !ins.opcode_idx.nil? } 569 tmp_private = initialize_instructions(opcodes) { |ins| ins.opcode_idx.nil? } 570 tmp = tmp_public + tmp_private 571 @instructions = tmp.sort_by(&:opcode_idx) 572 end 573 574 cached def prefixes 575 opcodes = PrefixOpcodeAssigner.new 576 tmp_public = initialize_prefixes(opcodes) { |p| !p.opcode_idx.nil? } 577 tmp_private = initialize_prefixes(opcodes) { |p| p.opcode_idx.nil? } 578 tmp = tmp_public + tmp_private 579 @prefixes = tmp.sort_by(&:opcode_idx) 580 end 581 582 def dispatch_table 583 DispatchTable.new 584 end 585 586 # Array of all Format instances 587 def formats 588 format_hash.values.uniq(&:pretty).sort_by(&:pretty) 589 end 590 591 # delegating part of module 592 # 593 def wrap_data(data) 594 @data = data 595 end 596 597 def respond_to_missing?(method_name, include_private = false) 598 @data.respond_to?(method_name, include_private) 599 end 600 601 def method_missing(method, *args, &block) 602 if respond_to_missing? method 603 @data.send(method, *args, &block) 604 else 605 super 606 end 607 end 608 609 # private functions 610 # 611 private_class_method def convert_to_hash(arr) 612 hash = arr.map { |i| [i.tag, i.description] }.to_h 613 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 614 hash 615 end 616 617 private_class_method def merge_group_and_insn(group, insn) 618 props = group.to_h 619 props.delete(:instructions) 620 props.merge(insn.to_h) do |_, old, new| 621 if old.is_a?(Array) && new.is_a?(Array) 622 old | new # extend array-like properties instead of overriding 623 else 624 new 625 end 626 end 627 end 628 629 private_class_method cached def each_data_instruction 630 # create separate instance for every instruction format and inherit group properties 631 groups.each_with_object([]) do |g, obj| 632 g.instructions.each do |i| 633 data_insn = merge_group_and_insn(g, i) 634 if data_insn[:opcode_idx] && (data_insn[:opcode_idx].size != data_insn[:format].size) 635 raise 'format and opcode_idx arrays should have equal size' 636 end 637 638 data_insn[:format].each_with_index do |f, idx| 639 insn = data_insn.dup 640 insn[:format] = f 641 insn[:opcode_idx] = data_insn[:opcode_idx][idx] if data_insn[:opcode_idx] 642 obj << OpenStruct.new(insn) 643 end 644 end 645 end.to_enum 646 end 647 648 private_class_method def initialize_instructions(opcodes, &block) 649 each_data_instruction.select(&block).each_with_object([]) do |instruction, insns| 650 insn = instruction.clone 651 insn[:public?] = !insn.opcode_idx.nil? 652 insn.opcode_idx = opcodes.yield_opcode(insn) 653 opcodes.consume(insn) 654 insns << Instruction.new(insn) 655 end 656 end 657 658 private_class_method def initialize_prefixes(opcodes, &block) 659 dig(:prefixes).select(&block).each_with_object([]) do |pref, res| 660 p = pref.clone 661 p[:public?] = !p.opcode_idx.nil? 662 p.opcode_idx = opcodes.yield_opcode(p) 663 opcodes.consume(p) 664 res << Prefix.new(p) 665 end 666 end 667end 668 669def Gen.on_require(data) 670 Panda.wrap_data(data) 671end 672