• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2021 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).size
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).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    props = i.properties
134    props.include?('acc_none') == !(props.include?('acc_read') || props.include?('acc_write'))
135  end.all?
136end
137
138assert('All calls write into accumulator') do
139  Panda.instructions.select { |i| i.properties.include?('call') }.map do |i|
140    i.properties.include?('acc_write')
141  end.all?
142end
143
144assert('Calls should be non-prefixed') do # otherwise support in interpreter-to-compiler bridges
145  Panda.instructions.select do |i|
146    i.properties.include?('call') && !i.mnemonic.include?('polymorphic')
147  end.select(&:prefix).empty?
148end
149
150assert('Jumps differ from other control-flow') do # At least currently
151  Panda.instructions.select { |i| i.mnemonic.match?(/^(throw|call|return)/) }.map do |i|
152    !i.jump?
153  end.all?
154end
155
156assert('Conversions should correspond to source and destination type') do
157  Panda.instructions.map do |i|
158    match = i.mnemonic.match(/[ifu](\d+)to[ifu](\d+)/)
159    next true unless match
160
161    ssize, dsize = match.captures
162    i.acc_and_operands.select(&:src?).first.type[1..-1].to_i >= ssize.to_i && i.dtype[1..-1].to_i >= dsize.to_i
163  end.all?
164end
165
166assert('Operand type should be one of none, ref, u1, u2, i8, u8, i16, u16, i32, u32, b32, i64, u64, b64, f64, top, any') do
167  types = %w[none ref u1 u2 i8 u8 i16 u16 i32 u32 b32 f32 i64 u64 b64 f64 top any]
168  Panda.instructions.map do |i|
169    i.acc_and_operands.all? { |op| types.include?(op.type.sub('[]', '')) }
170  end.all?
171end
172
173assert('Instruction should have not more than one destination') do
174  # Otherwise support it in Assembler IR
175  Panda.instructions.map do |i|
176    i.acc_and_operands.select(&:dst?).count <= 1
177  end.all?
178end
179
180assert('Instruction should have not more than one ID operand') do
181  # Otherwise support it in Assembler IR
182  Panda.instructions.map do |i|
183    i.operands.select(&:id?).count <= 1
184  end.all?
185end
186
187assert('Register encoding width should be the same in instruction') do
188  # Otherwise support it in Assembler
189  Panda.instructions.map do |i|
190    registers = i.operands.select(&:reg?)
191    registers.empty? || registers.map(&:width).uniq.one?
192  end.all?
193end
194
195assert('Calls should have call property') do
196  Panda.instructions.map do |i|
197    next true unless i.mnemonic.include?('call')
198
199    i.properties.include?('call')
200  end.all?
201end
202
203assert('Virtual calls should have call_virt property') do
204  Panda.instructions.map do |i|
205    next true unless i.mnemonic.include?('call.virt')
206
207    i.properties.include?('call_virt')
208  end.all?
209end
210