arkcompiler_runtime_core/isa/isapi.rb
xuhangqi 54adf1f81b Refactor verifier based on ruby
Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/IAS082

Signed-off-by: xuhangqi <xuhangqi@huawei.com>
Change-Id: I917d0bfa9ca348fd5023d56c4fe9603ebde2c6f4
2024-10-10 22:40:53 +08:00

668 lines
16 KiB
Ruby

# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'delegate'
require 'ostruct'
require 'digest'
require 'set'
module Enumerable
def stable_sort_by
sort_by.with_index { |x, idx| [yield(x), idx] }
end
end
module Util
module_function
def parse_acc_signature(sig)
res = []
if sig.include?('->')
src_type, dst_type = sig.match(/inout:(\w+)->(\w+)/).captures
res << Operand.new('acc', 'out', dst_type)
res << Operand.new('acc', 'in', src_type)
elsif sig.include?(':')
srcdst, type = sig.match(/(\w+):(\w+)/).captures
if srcdst == 'inout'
res << Operand.new('acc', 'out', type)
res << Operand.new('acc', 'in', type)
else
res << Operand.new('acc', srcdst, type)
end
elsif sig != 'none'
raise "Unexpected accumulator signature: #{sig}"
end
res
end
def parse_operand_signature(sig)
operand_parts = sig.split(':')
case operand_parts.size
when 1
name = operand_parts[0]
srcdst = :in
type = 'none'
when 2
name, type = operand_parts
srcdst = :in
when 3
name, srcdst, type = operand_parts
else
raise "Unexpected operand signature: #{sig}"
end
[name, srcdst, type]
end
end
module FreezeMixin
class << self
def included(base)
base.extend ClassMethods
end
end
module ClassMethods
def freeze_defined_methods
@frozen = instance_methods.map { |m| [m, true] }.to_h
end
def frozen?(name)
defined?(@frozen) && @frozen[name]
end
def method_added(name)
raise "Method '#{name}' has been already defined" if frozen?(name)
super
end
end
end
# Methods for YAML instructions
# 'Instruction' instances are created for every format of every isa.yaml
# instruction and inherit properties of its instruction group.
#
class Instruction < SimpleDelegator
# Signature without operands
def mnemonic
sig.split(' ')[0]
end
# Mnemonic stripped from type info
def stripped_mnemonic
mnemonic.split('.')[0]
end
# Unique (and not very long) identifier for all instructions
def opcode
mn = mnemonic.tr('.', '_')
fmt = format.pretty
if fmt == 'none'
mn
else
"#{mn}_#{fmt}"
end
end
def prefix
name = dig(:prefix)
Panda.prefixes_hash[name] if name
end
# Suggested handler name
def handler_name
opcode.upcase
end
def intrinsic_name
dig(:intrinsic_name)
end
def compilable?
properties.empty? || (!properties.include? 'not_compilable')
end
def inlinable?
properties.include? 'inlinable'
end
def opcode_idx
if prefix
dig(:opcode_idx) << 8 | prefix.opcode_idx
else
dig(:opcode_idx)
end
end
# Format instance for raw-data format name
def format
Panda.format_hash[dig(:format)]
end
# Array of explicit operands
cached def operands
return [] unless sig.include? ' '
_, operands = sig.match(/(\S+) (.+)/).captures
operands = operands.split(', ')
ops_encoding = format.encoding
count = 0
operands.map do |operand|
name, srcdst, type = Util.parse_operand_signature(operand)
if name.end_with?('id')
count += 1
end
end
id_count = 1
operands.map do |operand|
name, srcdst, type = Util.parse_operand_signature(operand)
key = name
if name.end_with?('id')
key = 'id'
if count > 1
key += id_count.to_s
id_count = id_count + 1
end
end
Operand.new(name, srcdst, type, ops_encoding[key].width, ops_encoding[key].offset)
end
end
# Used by compiler
# Operands array preceeded with accumulator as if it was a regular operand
# Registers that are both destination and source are uncoupled
cached def acc_and_operands
res = Util.parse_acc_signature(acc)
operands.each_with_object(res) do |op, ops|
if op.dst? && op.src?
ops << Operand.new(op.name, 'out', op.type, op.width, op.offset)
ops << Operand.new(op.name, 'in', op.type, op.width, op.offset)
else
ops << op
end
end
end
cached def properties
props = dig(:properties) || []
# Added for back compatibility:
add_props = []
add_props << 'acc_write' if acc_write?
add_props << 'acc_read' if acc_read?
add_props << 'acc_none' if acc_none?
props + add_props
end
cached def real_properties
filter = []
properties.each do |p|
if p != 'acc_write' && p != 'acc_read' && p != 'acc_none'
filter << p
end
end
filter << 'acc_write' if acc_write?
filter << 'acc_read' if acc_read?
filter << 'acc_none' if acc_none?
return filter
end
def type(index)
acc_and_operands.select(&:src?)[index].type || 'none'
end
# Type of single destination operand ("none" if there are no such)
def dtype
acc_and_operands.select(&:dst?).first&.type || 'none'
end
# Shortcut for querying 'float' property
def float?
properties.include? 'float'
end
# Shortcut for querying 'jump' property
def jump?
properties.include? 'jump'
end
# Shortcut for querying 'conditional' property
def conditional?
properties.include? 'conditional'
end
# Shortcut for querying 'x_none' exception
def throwing?
!exceptions.include? 'x_none'
end
def acc_read?
!acc_and_operands.select(&:acc?).select(&:src?).empty?
end
def acc_write?
!acc_and_operands.select(&:acc?).select(&:dst?).empty?
end
def acc_none?
acc_and_operands.select(&:acc?).empty?
end
def namespace
dig(:namespace) || 'core'
end
def is_range_0?
properties.include?('range_0')
end
def is_range_1?
properties.include?('range_1')
end
def is_range_instruction?
is_range_0? || is_range_1?
end
def is_return_instruction?
properties.include?('return')
end
def is_unconditional_throw_instruction?
dig(:prefix) == 'throw' && !properties.include?('conditional_throw')
end
include FreezeMixin
freeze_defined_methods
end
class Prefix < SimpleDelegator
# Suggested handler name
def handler_name
name.upcase
end
end
# Dummy class for invalid handlers
class Invalid
def handler_name
'INVALID'
end
end
# Methods over format names
#
class Format
attr_reader :name
def initialize(name)
@name = name
end
cached def pretty
name.sub('op_', '').gsub(/id[0-9]?/, 'id').gsub(/imm[0-9]?/, 'imm').gsub(/v[0-9]?/, 'v').gsub(/_([0-9]+)/, '\1')
end
def prefixed?
name.start_with?('pref_')
end
cached def size
bits = pretty.gsub(/[a-z]/, '').split('_').map(&:to_i).sum
raise "Incorrect format name #{name}" if bits % 8 != 0
opcode_bytes = prefixed? ? 2 : 1
bits / 8 + opcode_bytes
end
cached def encoding
return {} if name.end_with?('_none')
offset = prefixed? ? 16 : 8
encoding = {}
encoding.default_proc = proc { |_, k| raise KeyError, "#{k} not found" }
name.sub('pref_', '').sub('op_', '').split('_').each_slice(2).map do |name, width|
op = OpenStruct.new
op.name = name
op.width = width.to_i
op.offset = offset
offset += op.width
encoding[name] = op
end
encoding
end
include FreezeMixin
freeze_defined_methods
end
# Operand types and encoding
#
class Operand
attr_reader :name, :type, :offset, :width
def initialize(name, srcdst, type, width = 0, offset = 0)
@name = name.to_s.gsub(/[0-9]/, '').to_sym
unless %i[v acc imm method_id type_id field_id string_id literalarray_id].include?(@name)
raise "Incorrect operand #{name}"
end
@srcdst = srcdst.to_sym || :in
types = %i[none u1 u2 i8 u8 i16 u16 i32 u32 b32 f32 i64 u64 b64 f64 ref top any]
raise "Incorrect type #{type}" unless types.include?(type.sub('[]', '').to_sym)
@type = type
@width = width
@offset = offset
end
def reg?
@name == :v
end
def acc?
@name == :acc
end
def imm?
@name == :imm
end
def is_float_imm?
%i[f32 f64].include?(@type.to_sym)
end
def is_signed_imm?
%i[i8 i16 i32 i64].include?(@type.to_sym)
end
def is_unsigned_imm?
%i[u1 u2 u8 u16 u32 u64].include?(@type.to_sym)
end
def id?
%i[method_id type_id field_id string_id literalarray_id].include?(@name)
end
def method_id?
%i[method_id].include?(@name)
end
def string_id?
%i[string_id].include?(@name)
end
def literalarray_id?
%i[literalarray_id].include?(@name)
end
def dst?
%i[inout out].include?(@srcdst)
end
def src?
%i[inout in].include?(@srcdst)
end
def size
@type[1..-1].to_i
end
include FreezeMixin
freeze_defined_methods
end
# Helper class for generating dispatch tables
class DispatchTable
# Canonical order of dispatch table consisting of
# * non-prefixed instructions handlers
# * invalid handlers
# * prefix handlers that re-dispatch to prefixed instruction based on second byte of opcode_idx
# * prefixed instructions handlers, in the order of prefixes
# Return array with proposed handler names
def handler_names
handlers = Panda.instructions.reject(&:prefix) +
Array.new(invalid_non_prefixed_interval.size, Invalid.new) +
Panda.prefixes.select(&:public?) +
Array.new(invalid_prefixes_interval.size, Invalid.new) +
Panda.prefixes.reject(&:public?) +
Panda.instructions.select(&:prefix).stable_sort_by { |i| Panda.prefixes_hash[i.prefix.name].opcode_idx }
handlers.map(&:handler_name)
end
def invalid_non_prefixed_interval
(Panda.instructions.reject(&:prefix).map(&:opcode_idx).max + 1)..(Panda.prefixes.map(&:opcode_idx).min - 1)
end
def invalid_prefixes_interval
max_invalid_idx = Panda.prefixes.reject(&:public?).map(&:opcode_idx).min || 256
(Panda.prefixes.select(&:public?).map(&:opcode_idx).max + 1)..(max_invalid_idx - 1)
end
# Maximum value for secondary dispatch index for given prefix name
def secondary_opcode_bound(prefix)
prefix_data[prefix.name][:number_of_insns] - 1
end
# Offset in dispatch table for handlers of instructions for given prefix name
def secondary_opcode_offset(prefix)
256 + prefix_data[prefix.name][:delta]
end
private
cached def prefix_data
cur_delta = 0
Panda.prefixes.each_with_object({}) do |p, obj|
prefix_instructions_num = Panda.instructions.select { |i| i.prefix && (i.prefix.name == p.name) }.size
obj[p.name] = { delta: cur_delta, number_of_insns: prefix_instructions_num }
cur_delta += prefix_instructions_num
end
end
end
# Auxilary classes for opcode assignment
class OpcodeAssigner
def initialize
@table = Hash.new { |h, k| h[k] = Set.new }
@all_opcodes = Set.new(0..255)
end
def consume(item)
raise 'Cannot consume instruction without opcode' unless item.opcode_idx
@table[prefix(item)] << item.opcode_idx
end
def yield_opcode(item)
return item.opcode_idx if item.opcode_idx
opcodes = @table[prefix(item)]
choose_opcode(opcodes)
end
private
def choose_opcode(occupied_opcodes)
(@all_opcodes - occupied_opcodes).min
end
def prefix(item)
item.prefix.nil? ? 'non_prefixed' : item.prefix
end
end
class PrefixOpcodeAssigner < OpcodeAssigner
private
# override opcodes assignment for prefixes
def choose_opcode(occupied_opcodes)
(@all_opcodes - occupied_opcodes).max
end
end
# A bunch of handy methods for template generating
#
# All yaml properties are accessible by '.' syntax,
# e.g. 'Panda::groups[0].instruction[0].format'
#
module Panda
module_function
def properties
@data.properties +
[OpenStruct.new(tag: 'acc_none', description: 'Doesn\'t use accumulator register.'),
OpenStruct.new(tag: 'acc_read', description: 'Use accumulator as a first source operand.'),
OpenStruct.new(tag: 'acc_write', description: 'Use accumulator as a destination operand.')]
end
# Hash with exception tag as a key and exception description as a value
cached def exceptions_hash
convert_to_hash(exceptions)
end
# Hash with property tag as a key and property description as a value
cached def properties_hash
convert_to_hash(properties)
end
# Hash with verification tag as a key and verification description as a value
cached def verification_hash
convert_to_hash(verification)
end
cached def prefixes_hash
hash = prefixes.map { |p| [p.name, p] }.to_h
hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" }
hash
end
# Hash from format names to Format instances
cached def format_hash
each_data_instruction.with_object([]) do |instruction, fmts|
fmt_name = instruction.format
fmts << [fmt_name, Format.new(fmt_name)]
end.to_h
end
# Array of Instruction instances for every possible instruction
cached def instructions
opcodes = OpcodeAssigner.new
tmp_public = initialize_instructions(opcodes) { |ins| !ins.opcode_idx.nil? }
tmp_private = initialize_instructions(opcodes) { |ins| ins.opcode_idx.nil? }
tmp = tmp_public + tmp_private
@instructions = tmp.sort_by(&:opcode_idx)
end
cached def prefixes
opcodes = PrefixOpcodeAssigner.new
tmp_public = initialize_prefixes(opcodes) { |p| !p.opcode_idx.nil? }
tmp_private = initialize_prefixes(opcodes) { |p| p.opcode_idx.nil? }
tmp = tmp_public + tmp_private
@prefixes = tmp.sort_by(&:opcode_idx)
end
def dispatch_table
DispatchTable.new
end
# Array of all Format instances
def formats
format_hash.values.uniq(&:pretty).sort_by(&:pretty)
end
# delegating part of module
#
def wrap_data(data)
@data = data
end
def respond_to_missing?(method_name, include_private = false)
@data.respond_to?(method_name, include_private)
end
def method_missing(method, *args, &block)
if respond_to_missing? method
@data.send(method, *args, &block)
else
super
end
end
# private functions
#
private_class_method def convert_to_hash(arr)
hash = arr.map { |i| [i.tag, i.description] }.to_h
hash.default_proc = proc { |_, k| raise KeyError, "#{k} not found" }
hash
end
private_class_method def merge_group_and_insn(group, insn)
props = group.to_h
props.delete(:instructions)
props.merge(insn.to_h) do |_, old, new|
if old.is_a?(Array) && new.is_a?(Array)
old | new # extend array-like properties instead of overriding
else
new
end
end
end
private_class_method cached def each_data_instruction
# create separate instance for every instruction format and inherit group properties
groups.each_with_object([]) do |g, obj|
g.instructions.each do |i|
data_insn = merge_group_and_insn(g, i)
if data_insn[:opcode_idx] && (data_insn[:opcode_idx].size != data_insn[:format].size)
raise 'format and opcode_idx arrays should have equal size'
end
data_insn[:format].each_with_index do |f, idx|
insn = data_insn.dup
insn[:format] = f
insn[:opcode_idx] = data_insn[:opcode_idx][idx] if data_insn[:opcode_idx]
obj << OpenStruct.new(insn)
end
end
end.to_enum
end
private_class_method def initialize_instructions(opcodes, &block)
each_data_instruction.select(&block).each_with_object([]) do |instruction, insns|
insn = instruction.clone
insn[:public?] = !insn.opcode_idx.nil?
insn.opcode_idx = opcodes.yield_opcode(insn)
opcodes.consume(insn)
insns << Instruction.new(insn)
end
end
private_class_method def initialize_prefixes(opcodes, &block)
dig(:prefixes).select(&block).each_with_object([]) do |pref, res|
p = pref.clone
p[:public?] = !p.opcode_idx.nil?
p.opcode_idx = opcodes.yield_opcode(p)
opcodes.consume(p)
res << Prefix.new(p)
end
end
end
def Gen.on_require(data)
Panda.wrap_data(data)
end