• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
37 class InstructionReporter : public DecoderVisitor {
38  public:
InstructionReporter()39   InstructionReporter() : DecoderVisitor(kNonConstVisitor) {}
40 
Visit(Metadata * metadata,const Instruction * instr)41   void Visit(Metadata *metadata, const Instruction *instr) VIXL_OVERRIDE {
42     USE(instr);
43     instr_form_ = (*metadata)["form"];
44   }
45 
MoveForm()46   std::string MoveForm() { return std::move(instr_form_); }
47 
48  private:
49   std::string instr_form_;
50 };
51 
Mutate(Instr base)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
main(void)70 int main(void) {
71   printf("Test donkey requires a simulator build to be useful.\n");
72   return 0;
73 }
74 #else
main(int argc,char ** argv)75 int main(int argc, char **argv) {
76   if ((argc < 3) || (argc > 5)) {
77     printf(
78         "Usage: test-donkey <instruction form regex> <number of instructions "
79         "to emit in test> <encoding generation manner> <input data type>\n"
80         "  regex - ECMAScript (C++11) regular expression to match instruction "
81         "form\n"
82         "  encoding=random - use rng only to select new instructions\n"
83         "    (can take longer, but gives better coverage for disparate "
84         "encodings)\n"
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");
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;
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++;
116     }
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);
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();
173   SetInitialMachineState(&masm, input_set);
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();
229       SetInitialMachineState(&masm, input_set);
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");
295   printf((input_set == kFpInputSet)
296              ? "  SetInitialMachineState(&masm, kFpInputSet);\n"
297              : "  SetInitialMachineState(&masm);\n");
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
328