blob: c391323d5360a200fdf8c531bd78b1ce15b30011 [file] [log] [blame]
Martyn Capewelldba51cc2020-08-27 13:48:26 +01001// 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
32using namespace vixl;
33using namespace vixl::aarch64;
34
35#define __ masm->
36
37class InstructionReporter : public DecoderVisitorWithDefaults {
38 public:
39 InstructionReporter() : DecoderVisitorWithDefaults(kNonConstVisitor) {}
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
52Instr 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
70int main(void) {
71 printf("Test donkey requires a simulator build to be useful.\n");
72 return 0;
73}
74#else
75int main(int argc, char **argv) {
76 if ((argc != 3) && (argc != 4)) {
77 printf(
78 "Usage: test-donkey <instruction form regex> <number of instructions "
79 "to emit in test> "
80 "[random_only|initial encoding as hex]\n"
81 " regex - ECMAScript (C++11) regular expression to match instruction "
82 "form\n"
83 " random_only - use rng only to select new instructions\n"
84 " (can take longer, but gives better coverage for disparate "
85 "encodings)\n"
86 " initial encoding - encoding of first instruction in test, eg. "
87 "1234abcd\n");
88 exit(1);
89 }
90
91 // Use LC-RNG only to select instructions.
92 bool random_only = false;
93
94 std::string target_re = argv[1];
95 uint32_t count = static_cast<uint32_t>(strtoul(argv[2], NULL, 10));
96 uint32_t cmdline_encoding = 0;
97 if (argc == 4) {
98 if (strcmp(argv[3], "random_only") == 0) {
99 random_only = true;
100 } else {
101 cmdline_encoding = static_cast<uint32_t>(strtoul(argv[3], NULL, 16));
102 }
103 }
104
105 srand48(42);
106
107 MacroAssembler masm;
108 masm.GetCPUFeatures()->Combine(CPUFeatures::kSVE);
109
110 std::map<int, Simulator *> sim_vl;
111 for (int i = 128; i <= 2048; i += 128) {
112 sim_vl[i] = new Simulator(new Decoder());
113 sim_vl[i]->SetVectorLengthInBits(i);
114 }
115
116 char buffer[256];
117 Decoder trial_decoder;
118 Disassembler disasm(buffer, sizeof(buffer));
119 InstructionReporter reporter;
120 trial_decoder.AppendVisitor(&reporter);
121 trial_decoder.AppendVisitor(&disasm);
122
123 using InstrData = struct {
124 Instr inst;
125 std::string disasm;
126 uint32_t state_hash;
127 };
128 std::vector<InstrData> useful_insts;
129
130 // Seen states are only considered for vl128. It's assumed that a new state
131 // for vl128 implies a new state for all other vls.
132 std::set<uint32_t> seen_states;
133 uint32_t state_hash;
134
135 std::map<int, uint32_t> initial_state_vl;
136 std::map<int, uint32_t> state_hash_vl;
137
138 // Compute hash of the initial state of the machine.
139 Label test;
140 masm.Bind(&test);
141 masm.PushCalleeSavedRegisters();
142 SetInitialMachineState(&masm);
143 ComputeMachineStateHash(&masm, &state_hash);
144 masm.PopCalleeSavedRegisters();
145 masm.Ret();
146 masm.FinalizeCode();
147 masm.GetBuffer()->SetExecutable();
148
149 for (std::pair<int, Simulator *> s : sim_vl) {
150 s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test));
151 initial_state_vl[s.first] = state_hash;
152 if (s.first == 128) seen_states.insert(state_hash);
153 }
154
155 masm.GetBuffer()->SetWritable();
156 masm.Reset();
157
158 // Count number of failed instructions, in order to allow changing instruction
159 // candidate strategy.
160 int miss_count = 0;
161
162 while (useful_insts.size() < count) {
163 miss_count++;
164
165 Instr inst;
166 if (cmdline_encoding != 0) {
167 // Initial instruction encoding supplied on the command line.
168 inst = cmdline_encoding;
169 cmdline_encoding = 0;
170 } else if (useful_insts.empty() || random_only || (miss_count > 10000)) {
171 // LCG-random instruction.
172 inst = static_cast<Instr>(mrand48());
173 } else {
174 // Instruction based on mutation of last successful instruction.
175 inst = Mutate(useful_insts.back().inst);
176 }
177
178 trial_decoder.Decode(reinterpret_cast<Instruction *>(&inst));
179 if (std::regex_search(reporter.MoveForm(), std::regex(target_re))) {
180 // Disallow "unimplemented" instructions.
181 std::string buffer_s(buffer);
182 if (buffer_s.find("unimplemented") != std::string::npos) continue;
183
184 // Disallow instructions with "sp" in their arguments, as we don't support
185 // instructions operating on memory, and the OS expects sp to be valid for
186 // signal handlers, etc.
187 size_t space = buffer_s.find(' ');
188 if ((space != std::string::npos) &&
189 (buffer_s.substr(space).find("sp") != std::string::npos))
190 continue;
191
192 fprintf(stderr, "Trying 0x%08x (%s)\n", inst, buffer);
193
194 // TODO: factorise this code into a CalculateState helper function.
195
196 // Initialise the machine to a known state.
197 masm.PushCalleeSavedRegisters();
198 SetInitialMachineState(&masm);
199
200 {
201 ExactAssemblyScope scope(&masm,
202 (useful_insts.size() + 1) * kInstructionSize);
203
204 // Emit any instructions already found to move the state to somewhere
205 // new.
206 for (const InstrData &i : useful_insts) {
207 masm.dci(i.inst);
208 }
209
210 // Try a new instruction.
211 masm.dci(inst);
212 }
213
214 // Compute the new state of the machine.
215 ComputeMachineStateHash(&masm, &state_hash);
216 masm.PopCalleeSavedRegisters();
217 masm.Ret();
218 masm.FinalizeCode();
219 masm.GetBuffer()->SetExecutable();
220
221 // Try the new instruction for VL128.
222 sim_vl[128]->RunFrom(masm.GetLabelAddress<Instruction *>(&test));
223 state_hash_vl[128] = state_hash;
224
225 if (seen_states.count(state_hash_vl[128]) == 0) {
226 // A new state! Run for all VLs, record it, add the instruction to the
227 // list of useful ones.
228
229 for (std::pair<int, Simulator *> s : sim_vl) {
230 if (s.first == 128) continue;
231 s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test));
232 state_hash_vl[s.first] = state_hash;
233 }
234
235 seen_states.insert(state_hash_vl[128]);
236 useful_insts.push_back({inst, buffer, state_hash_vl[128]});
237 miss_count = 0;
238 } else {
239 // Machine already reached here. Probably not an interesting
240 // instruction. NB. it's possible for an instruction to reach the same
241 // machine state as two or more others, but for these purposes, let's
242 // call that not useful.
243 fprintf(stderr,
244 "Already reached state 0x%08x, skipping 0x%08x, miss_count "
245 "%d\n",
246 state_hash_vl[128],
247 inst,
248 miss_count);
249 }
250
251 // Restart generation.
252 masm.GetBuffer()->SetWritable();
253 masm.Reset();
254 }
255 }
256
257 // Emit test case based on identified instructions and associated hashes.
258 printf("TEST_SVE(sve2_%s) {\n", target_re.c_str());
259 printf(
260 " SVE_SETUP_WITH_FEATURES(CPUFeatures::kSVE, CPUFeatures::kSVE2, "
261 "CPUFeatures::kNEON, "
262 "CPUFeatures::kCRC32);\n");
263 printf(" START();\n\n");
264 printf(" SetInitialMachineState(&masm);\n");
265 printf(" // state = 0x%08x\n\n", initial_state_vl[128]);
266
267 printf(" {\n");
268 printf(" ExactAssemblyScope scope(&masm, %lu * kInstructionSize);\n",
269 useful_insts.size());
270 for (InstrData &i : useful_insts) {
271 printf(" __ dci(0x%08x); // %s\n", i.inst, i.disasm.c_str());
272 printf(" // vl128 state = 0x%08x\n", i.state_hash);
273 }
274 printf(" }\n\n");
275 printf(" uint32_t state;\n");
276 printf(" ComputeMachineStateHash(&masm, &state);\n");
277 printf(" __ Mov(x0, reinterpret_cast<uint64_t>(&state));\n");
278 printf(" __ Ldr(w0, MemOperand(x0));\n\n");
279 printf(" END();\n");
280 printf(" if (CAN_RUN()) {\n");
281 printf(" RUN();\n");
282 printf(" uint32_t expected_hashes[] = {\n");
283 for (std::pair<int, uint32_t> h : state_hash_vl) {
284 printf(" 0x%08x,\n", h.second);
285 }
286 printf(" };\n");
287 printf(
288 " ASSERT_EQUAL_64(expected_hashes[core.GetSVELaneCount(kQRegSize) - "
289 "1], x0);\n");
290 printf(" }\n}\n");
291
292 return 0;
293}
294#endif