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