• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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