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