• 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
14def assert(name)
15  loc = caller_locations(1, 1).first
16  raise "#{loc.path}:#{loc.lineno}: '#{name}' assertion failed" unless yield
17end
18
19module Enumerable
20  def sorted?
21    each_cons(2).all? { |a, b| (a <=> b) <= 0 }
22  end
23
24  def sorted_by?(&block)
25    map(&block).sorted?
26  end
27
28  def uniq?
29    uniq.size == size
30  end
31end
32
33assert('Unique opcodes') { Panda.instructions.map(&:opcode).uniq? }
34
35assert('Non-prefixed instruction opcodes and prefixes should fit one byte') do
36  Panda.instructions.reject(&:prefix).size + Panda.prefixes.size <= 256
37end
38
39assert('Non-prefixed instruction opcode indexes are sorted') do
40  Panda.instructions.reject(&:prefix).sorted_by?(&:opcode_idx)
41end
42
43assert('Prefix opcode indexes are sorted') do
44  Panda.prefixes.sorted_by?(&:opcode_idx)
45end
46
47assert('All instructions for a prefix should fit one byte') do
48  Panda.prefixes.map do |prefix|
49    Panda.instructions.select { |insn| insn.prefix && (insn.prefix.name == prefix.name) }.size <= 256
50  end.all?
51end
52
53assert('Prefixed instruction should have some prefix specified') do
54  Panda.instructions.map do |insn|
55    insn.format.prefixed? != insn.prefix.nil?
56  end.all?
57end
58
59assert('Prefix should be defined') do
60  Panda.instructions.map do |insn|
61    next true unless insn.prefix
62
63    Panda.prefixes.map(&:name).include?(insn.prefix.name)
64  end.all?
65end
66
67assert('All prefixes should have unique name') do
68  Panda.prefixes.map(&:name).uniq?
69end
70
71assert('There should be non-zero gap between non-prefixed and prefixes') do
72  !Panda.dispatch_table.invalid_non_prefixed_interval.to_a.empty?
73end
74
75assert('There should be non-zero gap between public and private prefixes') do
76  !Panda.dispatch_table.invalid_prefixes_interval.to_a.empty?
77end
78
79assert('All tags are unique between categories') do
80  %i[verification exceptions properties].flat_map { |type| Panda.send(type).map(&:tag) }.uniq?
81end
82
83assert('All tags are used') do
84  %i[verification exceptions properties].map do |type|
85    uses = Panda.instructions.flat_map(&type.to_proc).uniq
86    defs = Panda.send(type).map(&:tag)
87    (defs - uses - ['suspend']).size # 'suspend' is non-core optional property, allowed to be unused
88  end.reduce(:+).zero?
89end
90
91assert('All tags are defined') do
92  %i[verification exceptions properties].map do |type|
93    uses = Panda.instructions.flat_map(&type.to_proc).uniq
94    defs = Panda.send(type).map(&:tag)
95    (uses - defs - ['acc_read', 'acc_write', 'acc_none']).size
96  end.reduce(:+).zero?
97end
98
99assert('Format operands are parseable') { Panda.instructions.each(&:operands) }
100
101assert('Verification, exceptions and properties are not empty for every instruction group') do
102  %i[verification exceptions properties].map do |type|
103    !Panda.groups.map(&type).empty?
104  end.all?
105end
106
107assert('Mnemonic defines operand types') do
108  Panda.instructions.group_by(&:mnemonic).map do |_, insns|
109    insns.map { |insn| insn.operands.map(&:name) }.uniq.one?
110  end.all?
111end
112
113assert('Dtype should be none when bytecode doesn\'t write into accumulator or registers') do
114  Panda.instructions.map do |i|
115    next true if i.properties.include?('language')
116
117    i.acc_and_operands.map(&:dst?).any? == (i.dtype != 'none')
118  end.all?
119end
120
121assert('Instruction::float? should play well with operand types') do
122  Panda.instructions.map do |i|
123    i.float? == i.acc_and_operands.any? { |op| op.type.start_with?('f') }
124  end.all?
125end
126
127assert('Conditionals should be jumps') do # At least currently
128  Panda.instructions.select(&:conditional?).map(&:jump?).all?
129end
130
131assert('Acc_none should not be specified along with other accumulator properties') do
132  Panda.instructions.map do |i|
133    i.acc_none? == !(i.acc_read? || i.acc_write?)
134  end.all?
135end
136
137assert('All calls write into accumulator') do
138  Panda.instructions.select { |i| i.properties.include?('call') }.map(&:acc_write?).all?
139end
140
141assert('Jumps differ from other control-flow') do # At least currently
142  Panda.instructions.select { |i| i.mnemonic.match?(/^(throw|call|return)/) }.map do |i|
143    !i.jump?
144  end.all?
145end
146
147assert('Conversions should correspond to source and destination type') do
148  Panda.instructions.map do |i|
149    match = i.mnemonic.match(/[ifu](\d+)to[ifu](\d+)/)
150    next true unless match
151
152    ssize, dsize = match.captures
153    i.acc_and_operands.select(&:src?).first.type[1..-1].to_i >= ssize.to_i && i.dtype[1..-1].to_i >= dsize.to_i
154  end.all?
155end
156
157assert('Operand type should be one of none, ref, u1, u2, i8, u8, i16, u16, i32, u32, b32, i64, u64, b64, f64, top, any') do
158  types = %w[none ref u1 u2 i8 u8 i16 u16 i32 u32 b32 f32 i64 u64 b64 f64 top any]
159  Panda.instructions.map do |i|
160    i.acc_and_operands.all? { |op| types.include?(op.type.sub('[]', '')) }
161  end.all?
162end
163
164assert('Instruction should have not more than one destination') do
165  # Otherwise support it in Assembler IR
166  Panda.instructions.map do |i|
167    i.acc_and_operands.select(&:dst?).count <= 1
168  end.all?
169end
170
171assert('Instruction should have not more than one ID operand') do
172  # Otherwise support it in Assembler IR
173  Panda.instructions.map do |i|
174    i.operands.select(&:id?).count <= 1
175  end.all?
176end
177
178assert('Register encoding width should be the same in instruction') do
179  # Otherwise support it in Assembler
180  Panda.instructions.map do |i|
181    registers = i.operands.select(&:reg?)
182    registers.empty? || registers.map(&:width).uniq.one?
183  end.all?
184end
185
186assert('Calls should have call property and x_call exception tag') do
187  Panda.instructions.map do |i|
188    next true unless i.mnemonic.start_with?('call')
189
190    i.properties.include?('call') && i.exceptions.include?('x_call')
191  end.all?
192end
193
194assert('Virtual calls should have call_virt property') do
195  Panda.instructions.map do |i|
196    next true unless i.mnemonic.include?('call.virt')
197
198    i.properties.include?('call_virt')
199  end.all?
200end
201