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 id? 364 %i[method_id type_id field_id string_id literalarray_id].include?(@name) 365 end 366 367 def method_id? 368 %i[method_id].include?(@name) 369 end 370 371 def string_id? 372 %i[string_id].include?(@name) 373 end 374 375 def literalarray_id? 376 %i[literalarray_id].include?(@name) 377 end 378 379 def dst? 380 %i[inout out].include?(@srcdst) 381 end 382 383 def src? 384 %i[inout in].include?(@srcdst) 385 end 386 387 def size 388 @type[1..-1].to_i 389 end 390 391 include FreezeMixin 392 freeze_defined_methods 393end 394 395# Helper class for generating dispatch tables 396class DispatchTable 397 # Canonical order of dispatch table consisting of 398 # * non-prefixed instructions handlers 399 # * invalid handlers 400 # * prefix handlers that re-dispatch to prefixed instruction based on second byte of opcode_idx 401 # * prefixed instructions handlers, in the order of prefixes 402 # Return array with proposed handler names 403 def handler_names 404 handlers = Panda.instructions.reject(&:prefix) + 405 Array.new(invalid_non_prefixed_interval.size, Invalid.new) + 406 Panda.prefixes.select(&:public?) + 407 Array.new(invalid_prefixes_interval.size, Invalid.new) + 408 Panda.prefixes.reject(&:public?) + 409 Panda.instructions.select(&:prefix).stable_sort_by { |i| Panda.prefixes_hash[i.prefix.name].opcode_idx } 410 411 handlers.map(&:handler_name) 412 end 413 414 def invalid_non_prefixed_interval 415 (Panda.instructions.reject(&:prefix).map(&:opcode_idx).max + 1)..(Panda.prefixes.map(&:opcode_idx).min - 1) 416 end 417 418 def invalid_prefixes_interval 419 max_invalid_idx = Panda.prefixes.reject(&:public?).map(&:opcode_idx).min || 256 420 (Panda.prefixes.select(&:public?).map(&:opcode_idx).max + 1)..(max_invalid_idx - 1) 421 end 422 423 # Maximum value for secondary dispatch index for given prefix name 424 def secondary_opcode_bound(prefix) 425 prefix_data[prefix.name][:number_of_insns] - 1 426 end 427 428 # Offset in dispatch table for handlers of instructions for given prefix name 429 def secondary_opcode_offset(prefix) 430 256 + prefix_data[prefix.name][:delta] 431 end 432 433 private 434 435 cached def prefix_data 436 cur_delta = 0 437 Panda.prefixes.each_with_object({}) do |p, obj| 438 prefix_instructions_num = Panda.instructions.select { |i| i.prefix && (i.prefix.name == p.name) }.size 439 obj[p.name] = { delta: cur_delta, number_of_insns: prefix_instructions_num } 440 cur_delta += prefix_instructions_num 441 end 442 end 443end 444 445# Auxilary classes for opcode assignment 446class OpcodeAssigner 447 def initialize 448 @table = Hash.new { |h, k| h[k] = Set.new } 449 @all_opcodes = Set.new(0..255) 450 end 451 452 def consume(item) 453 raise 'Cannot consume instruction without opcode' unless item.opcode_idx 454 455 @table[prefix(item)] << item.opcode_idx 456 end 457 458 def yield_opcode(item) 459 return item.opcode_idx if item.opcode_idx 460 461 opcodes = @table[prefix(item)] 462 choose_opcode(opcodes) 463 end 464 465 private 466 467 def choose_opcode(occupied_opcodes) 468 (@all_opcodes - occupied_opcodes).min 469 end 470 471 def prefix(item) 472 item.prefix.nil? ? 'non_prefixed' : item.prefix 473 end 474end 475 476class PrefixOpcodeAssigner < OpcodeAssigner 477 private 478 479 # override opcodes assignment for prefixes 480 def choose_opcode(occupied_opcodes) 481 (@all_opcodes - occupied_opcodes).max 482 end 483end 484 485# A bunch of handy methods for template generating 486# 487# All yaml properties are accessible by '.' syntax, 488# e.g. 'Panda::groups[0].instruction[0].format' 489# 490module Panda 491 module_function 492 493 def properties 494 @data.properties + 495 [OpenStruct.new(tag: 'acc_none', description: 'Doesn\'t use accumulator register.'), 496 OpenStruct.new(tag: 'acc_read', description: 'Use accumulator as a first source operand.'), 497 OpenStruct.new(tag: 'acc_write', description: 'Use accumulator as a destination operand.')] 498 end 499 500 # Hash with exception tag as a key and exception description as a value 501 cached def exceptions_hash 502 convert_to_hash(exceptions) 503 end 504 505 # Hash with property tag as a key and property description as a value 506 cached def properties_hash 507 convert_to_hash(properties) 508 end 509 510 # Hash with verification tag as a key and verification description as a value 511 cached def verification_hash 512 convert_to_hash(verification) 513 end 514 515 cached def prefixes_hash 516 hash = prefixes.map { |p| [p.name, p] }.to_h 517 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 518 hash 519 end 520 521 # Hash from format names to Format instances 522 cached def format_hash 523 each_data_instruction.with_object([]) do |instruction, fmts| 524 fmt_name = instruction.format 525 fmts << [fmt_name, Format.new(fmt_name)] 526 end.to_h 527 end 528 529 # Array of Instruction instances for every possible instruction 530 cached def instructions 531 opcodes = OpcodeAssigner.new 532 tmp_public = initialize_instructions(opcodes) { |ins| !ins.opcode_idx.nil? } 533 tmp_private = initialize_instructions(opcodes) { |ins| ins.opcode_idx.nil? } 534 tmp = tmp_public + tmp_private 535 @instructions = tmp.sort_by(&:opcode_idx) 536 end 537 538 cached def prefixes 539 opcodes = PrefixOpcodeAssigner.new 540 tmp_public = initialize_prefixes(opcodes) { |p| !p.opcode_idx.nil? } 541 tmp_private = initialize_prefixes(opcodes) { |p| p.opcode_idx.nil? } 542 tmp = tmp_public + tmp_private 543 @prefixes = tmp.sort_by(&:opcode_idx) 544 end 545 546 def dispatch_table 547 DispatchTable.new 548 end 549 550 # Array of all Format instances 551 def formats 552 format_hash.values.uniq(&:pretty).sort_by(&:pretty) 553 end 554 555 # delegating part of module 556 # 557 def wrap_data(data) 558 @data = data 559 end 560 561 def respond_to_missing?(method_name, include_private = false) 562 @data.respond_to?(method_name, include_private) 563 end 564 565 def method_missing(method, *args, &block) 566 if respond_to_missing? method 567 @data.send(method, *args, &block) 568 else 569 super 570 end 571 end 572 573 # private functions 574 # 575 private_class_method def convert_to_hash(arr) 576 hash = arr.map { |i| [i.tag, i.description] }.to_h 577 hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" } 578 hash 579 end 580 581 private_class_method def merge_group_and_insn(group, insn) 582 props = group.to_h 583 props.delete(:instructions) 584 props.merge(insn.to_h) do |_, old, new| 585 if old.is_a?(Array) && new.is_a?(Array) 586 old | new # extend array-like properties instead of overriding 587 else 588 new 589 end 590 end 591 end 592 593 private_class_method cached def each_data_instruction 594 # create separate instance for every instruction format and inherit group properties 595 groups.each_with_object([]) do |g, obj| 596 g.instructions.each do |i| 597 data_insn = merge_group_and_insn(g, i) 598 if data_insn[:opcode_idx] && (data_insn[:opcode_idx].size != data_insn[:format].size) 599 raise 'format and opcode_idx arrays should have equal size' 600 end 601 602 data_insn[:format].each_with_index do |f, idx| 603 insn = data_insn.dup 604 insn[:format] = f 605 insn[:opcode_idx] = data_insn[:opcode_idx][idx] if data_insn[:opcode_idx] 606 obj << OpenStruct.new(insn) 607 end 608 end 609 end.to_enum 610 end 611 612 private_class_method def initialize_instructions(opcodes, &block) 613 each_data_instruction.select(&block).each_with_object([]) do |instruction, insns| 614 insn = instruction.clone 615 insn[:public?] = !insn.opcode_idx.nil? 616 insn.opcode_idx = opcodes.yield_opcode(insn) 617 opcodes.consume(insn) 618 insns << Instruction.new(insn) 619 end 620 end 621 622 private_class_method def initialize_prefixes(opcodes, &block) 623 dig(:prefixes).select(&block).each_with_object([]) do |pref, res| 624 p = pref.clone 625 p[:public?] = !p.opcode_idx.nil? 626 p.opcode_idx = opcodes.yield_opcode(p) 627 opcodes.consume(p) 628 res << Prefix.new(p) 629 end 630 end 631end 632 633def Gen.on_require(data) 634 Panda.wrap_data(data) 635end 636