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