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