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