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