Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 1 | // Copyright 2020, VIXL authors |
| 2 | // All rights reserved. |
| 3 | // |
| 4 | // Redistribution and use in source and binary forms, with or without |
| 5 | // modification, are permitted provided that the following conditions are met: |
| 6 | // |
| 7 | // * Redistributions of source code must retain the above copyright notice, |
| 8 | // this list of conditions and the following disclaimer. |
| 9 | // * Redistributions in binary form must reproduce the above copyright notice, |
| 10 | // this list of conditions and the following disclaimer in the documentation |
| 11 | // and/or other materials provided with the distribution. |
| 12 | // * Neither the name of ARM Limited nor the names of its contributors may be |
| 13 | // used to endorse or promote products derived from this software without |
| 14 | // specific prior written permission. |
| 15 | // |
| 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND |
| 17 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 18 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 19 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
| 20 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 21 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 22 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 23 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 24 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | |
| 27 | #include <regex> |
| 28 | #include <set> |
| 29 | |
| 30 | #include "aarch64/test-utils-aarch64.h" |
| 31 | |
| 32 | using namespace vixl; |
| 33 | using namespace vixl::aarch64; |
| 34 | |
| 35 | #define __ masm-> |
| 36 | |
Martyn Capewell | c20aa62 | 2021-05-04 16:34:49 +0100 | [diff] [blame] | 37 | class InstructionReporter : public DecoderVisitor { |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 38 | public: |
Martyn Capewell | c20aa62 | 2021-05-04 16:34:49 +0100 | [diff] [blame] | 39 | InstructionReporter() : DecoderVisitor(kNonConstVisitor) {} |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 40 | |
| 41 | void Visit(Metadata *metadata, const Instruction *instr) VIXL_OVERRIDE { |
| 42 | USE(instr); |
| 43 | instr_form_ = (*metadata)["form"]; |
| 44 | } |
| 45 | |
| 46 | std::string MoveForm() { return std::move(instr_form_); } |
| 47 | |
| 48 | private: |
| 49 | std::string instr_form_; |
| 50 | }; |
| 51 | |
| 52 | Instr Mutate(Instr base) { |
| 53 | Instr result = base; |
| 54 | while ((result == base) || (result == 0)) { |
| 55 | // Flip two bits somewhere in the most-significant 27. |
| 56 | for (int i = 0; i < 2; i++) { |
| 57 | uint32_t pos = 5 + ((lrand48() >> 20) % 27); |
| 58 | result = result ^ (1 << pos); |
| 59 | } |
| 60 | |
| 61 | // Always flip one of the low five bits, as that's where the destination |
| 62 | // register is often encoded. |
| 63 | uint32_t dst_pos = (lrand48() >> 20) % 5; |
| 64 | result = result ^ (1 << dst_pos); |
| 65 | } |
| 66 | return result; |
| 67 | } |
| 68 | |
| 69 | #ifndef VIXL_INCLUDE_SIMULATOR_AARCH64 |
| 70 | int main(void) { |
| 71 | printf("Test donkey requires a simulator build to be useful.\n"); |
| 72 | return 0; |
| 73 | } |
| 74 | #else |
| 75 | int main(int argc, char **argv) { |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 76 | if ((argc < 3) || (argc > 5)) { |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 77 | printf( |
| 78 | "Usage: test-donkey <instruction form regex> <number of instructions " |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 79 | "to emit in test> <encoding generation manner> <input data type>\n" |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 80 | " regex - ECMAScript (C++11) regular expression to match instruction " |
| 81 | "form\n" |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 82 | " encoding=random - use rng only to select new instructions\n" |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 83 | " (can take longer, but gives better coverage for disparate " |
| 84 | "encodings)\n" |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 85 | " encoding=`initial hex` - hex encoding of first instruction in test, " |
| 86 | "eg. 1234abcd\n" |
| 87 | " input data type - used to specify the data type of generating " |
| 88 | "input, e.g. input=fp, default set to integer type\n" |
| 89 | " command examples :\n" |
| 90 | " ./test-donkey \"fml[as]l[bt]\" 50 encoding=random input=fp\n" |
| 91 | " ./test-donkey \"fml[as]l[bt]\" 30 input=int\n"); |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 92 | exit(1); |
| 93 | } |
| 94 | |
| 95 | // Use LC-RNG only to select instructions. |
| 96 | bool random_only = false; |
| 97 | |
| 98 | std::string target_re = argv[1]; |
| 99 | uint32_t count = static_cast<uint32_t>(strtoul(argv[2], NULL, 10)); |
| 100 | uint32_t cmdline_encoding = 0; |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 101 | InputSet input_set = kIntInputSet; |
| 102 | if (argc > 3) { |
| 103 | // The arguments of instruction pattern and the number of generating |
| 104 | // instructions are processed. |
| 105 | int32_t i = 3; |
| 106 | std::string argv_s(argv[i]); |
| 107 | if (argv_s.find("encoding=") != std::string::npos) { |
| 108 | char *c = argv[i]; |
| 109 | c += 9; |
| 110 | if (strcmp(c, "random") == 0) { |
| 111 | random_only = true; |
| 112 | } else { |
| 113 | cmdline_encoding = static_cast<uint32_t>(strtoul(c, NULL, 16)); |
| 114 | } |
| 115 | i++; |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 116 | } |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 117 | |
| 118 | if ((argc > 4) || (i == 3)) { |
| 119 | argv_s = std::string(argv[i]); |
| 120 | if (argv_s.find("input=") != std::string::npos) { |
| 121 | char *c = argv[i]; |
| 122 | c += 6; |
| 123 | if (strcmp(c, "fp") == 0) { |
| 124 | input_set = kFpInputSet; |
| 125 | } else { |
| 126 | VIXL_ASSERT(strcmp(c, "int") == 0); |
| 127 | } |
| 128 | i++; |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // Ensure all arguments have been processed. |
| 133 | VIXL_ASSERT(argc == i); |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | srand48(42); |
| 137 | |
| 138 | MacroAssembler masm; |
| 139 | masm.GetCPUFeatures()->Combine(CPUFeatures::kSVE); |
| 140 | |
| 141 | std::map<int, Simulator *> sim_vl; |
| 142 | for (int i = 128; i <= 2048; i += 128) { |
| 143 | sim_vl[i] = new Simulator(new Decoder()); |
| 144 | sim_vl[i]->SetVectorLengthInBits(i); |
| 145 | } |
| 146 | |
| 147 | char buffer[256]; |
| 148 | Decoder trial_decoder; |
| 149 | Disassembler disasm(buffer, sizeof(buffer)); |
| 150 | InstructionReporter reporter; |
| 151 | trial_decoder.AppendVisitor(&reporter); |
| 152 | trial_decoder.AppendVisitor(&disasm); |
| 153 | |
| 154 | using InstrData = struct { |
| 155 | Instr inst; |
| 156 | std::string disasm; |
| 157 | uint32_t state_hash; |
| 158 | }; |
| 159 | std::vector<InstrData> useful_insts; |
| 160 | |
| 161 | // Seen states are only considered for vl128. It's assumed that a new state |
| 162 | // for vl128 implies a new state for all other vls. |
| 163 | std::set<uint32_t> seen_states; |
| 164 | uint32_t state_hash; |
| 165 | |
| 166 | std::map<int, uint32_t> initial_state_vl; |
| 167 | std::map<int, uint32_t> state_hash_vl; |
| 168 | |
| 169 | // Compute hash of the initial state of the machine. |
| 170 | Label test; |
| 171 | masm.Bind(&test); |
| 172 | masm.PushCalleeSavedRegisters(); |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 173 | SetInitialMachineState(&masm, input_set); |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 174 | ComputeMachineStateHash(&masm, &state_hash); |
| 175 | masm.PopCalleeSavedRegisters(); |
| 176 | masm.Ret(); |
| 177 | masm.FinalizeCode(); |
| 178 | masm.GetBuffer()->SetExecutable(); |
| 179 | |
| 180 | for (std::pair<int, Simulator *> s : sim_vl) { |
| 181 | s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); |
| 182 | initial_state_vl[s.first] = state_hash; |
| 183 | if (s.first == 128) seen_states.insert(state_hash); |
| 184 | } |
| 185 | |
| 186 | masm.GetBuffer()->SetWritable(); |
| 187 | masm.Reset(); |
| 188 | |
| 189 | // Count number of failed instructions, in order to allow changing instruction |
| 190 | // candidate strategy. |
| 191 | int miss_count = 0; |
| 192 | |
| 193 | while (useful_insts.size() < count) { |
| 194 | miss_count++; |
| 195 | |
| 196 | Instr inst; |
| 197 | if (cmdline_encoding != 0) { |
| 198 | // Initial instruction encoding supplied on the command line. |
| 199 | inst = cmdline_encoding; |
| 200 | cmdline_encoding = 0; |
| 201 | } else if (useful_insts.empty() || random_only || (miss_count > 10000)) { |
| 202 | // LCG-random instruction. |
| 203 | inst = static_cast<Instr>(mrand48()); |
| 204 | } else { |
| 205 | // Instruction based on mutation of last successful instruction. |
| 206 | inst = Mutate(useful_insts.back().inst); |
| 207 | } |
| 208 | |
| 209 | trial_decoder.Decode(reinterpret_cast<Instruction *>(&inst)); |
| 210 | if (std::regex_search(reporter.MoveForm(), std::regex(target_re))) { |
| 211 | // Disallow "unimplemented" instructions. |
| 212 | std::string buffer_s(buffer); |
| 213 | if (buffer_s.find("unimplemented") != std::string::npos) continue; |
| 214 | |
| 215 | // Disallow instructions with "sp" in their arguments, as we don't support |
| 216 | // instructions operating on memory, and the OS expects sp to be valid for |
| 217 | // signal handlers, etc. |
| 218 | size_t space = buffer_s.find(' '); |
| 219 | if ((space != std::string::npos) && |
| 220 | (buffer_s.substr(space).find("sp") != std::string::npos)) |
| 221 | continue; |
| 222 | |
| 223 | fprintf(stderr, "Trying 0x%08x (%s)\n", inst, buffer); |
| 224 | |
| 225 | // TODO: factorise this code into a CalculateState helper function. |
| 226 | |
| 227 | // Initialise the machine to a known state. |
| 228 | masm.PushCalleeSavedRegisters(); |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 229 | SetInitialMachineState(&masm, input_set); |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 230 | |
| 231 | { |
| 232 | ExactAssemblyScope scope(&masm, |
| 233 | (useful_insts.size() + 1) * kInstructionSize); |
| 234 | |
| 235 | // Emit any instructions already found to move the state to somewhere |
| 236 | // new. |
| 237 | for (const InstrData &i : useful_insts) { |
| 238 | masm.dci(i.inst); |
| 239 | } |
| 240 | |
| 241 | // Try a new instruction. |
| 242 | masm.dci(inst); |
| 243 | } |
| 244 | |
| 245 | // Compute the new state of the machine. |
| 246 | ComputeMachineStateHash(&masm, &state_hash); |
| 247 | masm.PopCalleeSavedRegisters(); |
| 248 | masm.Ret(); |
| 249 | masm.FinalizeCode(); |
| 250 | masm.GetBuffer()->SetExecutable(); |
| 251 | |
| 252 | // Try the new instruction for VL128. |
| 253 | sim_vl[128]->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); |
| 254 | state_hash_vl[128] = state_hash; |
| 255 | |
| 256 | if (seen_states.count(state_hash_vl[128]) == 0) { |
| 257 | // A new state! Run for all VLs, record it, add the instruction to the |
| 258 | // list of useful ones. |
| 259 | |
| 260 | for (std::pair<int, Simulator *> s : sim_vl) { |
| 261 | if (s.first == 128) continue; |
| 262 | s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); |
| 263 | state_hash_vl[s.first] = state_hash; |
| 264 | } |
| 265 | |
| 266 | seen_states.insert(state_hash_vl[128]); |
| 267 | useful_insts.push_back({inst, buffer, state_hash_vl[128]}); |
| 268 | miss_count = 0; |
| 269 | } else { |
| 270 | // Machine already reached here. Probably not an interesting |
| 271 | // instruction. NB. it's possible for an instruction to reach the same |
| 272 | // machine state as two or more others, but for these purposes, let's |
| 273 | // call that not useful. |
| 274 | fprintf(stderr, |
| 275 | "Already reached state 0x%08x, skipping 0x%08x, miss_count " |
| 276 | "%d\n", |
| 277 | state_hash_vl[128], |
| 278 | inst, |
| 279 | miss_count); |
| 280 | } |
| 281 | |
| 282 | // Restart generation. |
| 283 | masm.GetBuffer()->SetWritable(); |
| 284 | masm.Reset(); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | // Emit test case based on identified instructions and associated hashes. |
| 289 | printf("TEST_SVE(sve2_%s) {\n", target_re.c_str()); |
| 290 | printf( |
| 291 | " SVE_SETUP_WITH_FEATURES(CPUFeatures::kSVE, CPUFeatures::kSVE2, " |
| 292 | "CPUFeatures::kNEON, " |
| 293 | "CPUFeatures::kCRC32);\n"); |
| 294 | printf(" START();\n\n"); |
TatWai Chong | ba9a148 | 2020-10-01 20:25:54 -0700 | [diff] [blame] | 295 | printf((input_set == kFpInputSet) |
| 296 | ? " SetInitialMachineState(&masm, kFpInputSet);\n" |
| 297 | : " SetInitialMachineState(&masm);\n"); |
Martyn Capewell | dba51cc | 2020-08-27 13:48:26 +0100 | [diff] [blame] | 298 | printf(" // state = 0x%08x\n\n", initial_state_vl[128]); |
| 299 | |
| 300 | printf(" {\n"); |
| 301 | printf(" ExactAssemblyScope scope(&masm, %lu * kInstructionSize);\n", |
| 302 | useful_insts.size()); |
| 303 | for (InstrData &i : useful_insts) { |
| 304 | printf(" __ dci(0x%08x); // %s\n", i.inst, i.disasm.c_str()); |
| 305 | printf(" // vl128 state = 0x%08x\n", i.state_hash); |
| 306 | } |
| 307 | printf(" }\n\n"); |
| 308 | printf(" uint32_t state;\n"); |
| 309 | printf(" ComputeMachineStateHash(&masm, &state);\n"); |
| 310 | printf(" __ Mov(x0, reinterpret_cast<uint64_t>(&state));\n"); |
| 311 | printf(" __ Ldr(w0, MemOperand(x0));\n\n"); |
| 312 | printf(" END();\n"); |
| 313 | printf(" if (CAN_RUN()) {\n"); |
| 314 | printf(" RUN();\n"); |
| 315 | printf(" uint32_t expected_hashes[] = {\n"); |
| 316 | for (std::pair<int, uint32_t> h : state_hash_vl) { |
| 317 | printf(" 0x%08x,\n", h.second); |
| 318 | } |
| 319 | printf(" };\n"); |
| 320 | printf( |
| 321 | " ASSERT_EQUAL_64(expected_hashes[core.GetSVELaneCount(kQRegSize) - " |
| 322 | "1], x0);\n"); |
| 323 | printf(" }\n}\n"); |
| 324 | |
| 325 | return 0; |
| 326 | } |
| 327 | #endif |