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