diff options
Diffstat (limited to 'test/test-donkey.cc')
-rw-r--r-- | test/test-donkey.cc | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/test/test-donkey.cc b/test/test-donkey.cc new file mode 100644 index 00000000..250fa5d3 --- /dev/null +++ b/test/test-donkey.cc @@ -0,0 +1,327 @@ +// Copyright 2020, VIXL authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of ARM Limited nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <regex> +#include <set> + +#include "aarch64/test-utils-aarch64.h" + +using namespace vixl; +using namespace vixl::aarch64; + +#define __ masm-> + +class InstructionReporter : public DecoderVisitor { + public: + InstructionReporter() : DecoderVisitor(kNonConstVisitor) {} + + void Visit(Metadata *metadata, const Instruction *instr) VIXL_OVERRIDE { + USE(instr); + instr_form_ = (*metadata)["form"]; + } + + std::string MoveForm() { return std::move(instr_form_); } + + private: + std::string instr_form_; +}; + +Instr Mutate(Instr base) { + Instr result = base; + while ((result == base) || (result == 0)) { + // Flip two bits somewhere in the most-significant 27. + for (int i = 0; i < 2; i++) { + uint32_t pos = 5 + ((lrand48() >> 20) % 27); + result = result ^ (1 << pos); + } + + // Always flip one of the low five bits, as that's where the destination + // register is often encoded. + uint32_t dst_pos = (lrand48() >> 20) % 5; + result = result ^ (1 << dst_pos); + } + return result; +} + +#ifndef VIXL_INCLUDE_SIMULATOR_AARCH64 +int main(void) { + printf("Test donkey requires a simulator build to be useful.\n"); + return 0; +} +#else +int main(int argc, char **argv) { + if ((argc < 3) || (argc > 5)) { + printf( + "Usage: test-donkey <instruction form regex> <number of instructions " + "to emit in test> <encoding generation manner> <input data type>\n" + " regex - ECMAScript (C++11) regular expression to match instruction " + "form\n" + " encoding=random - use rng only to select new instructions\n" + " (can take longer, but gives better coverage for disparate " + "encodings)\n" + " encoding=`initial hex` - hex encoding of first instruction in test, " + "eg. 1234abcd\n" + " input data type - used to specify the data type of generating " + "input, e.g. input=fp, default set to integer type\n" + " command examples :\n" + " ./test-donkey \"fml[as]l[bt]\" 50 encoding=random input=fp\n" + " ./test-donkey \"fml[as]l[bt]\" 30 input=int\n"); + exit(1); + } + + // Use LC-RNG only to select instructions. + bool random_only = false; + + std::string target_re = argv[1]; + uint32_t count = static_cast<uint32_t>(strtoul(argv[2], NULL, 10)); + uint32_t cmdline_encoding = 0; + InputSet input_set = kIntInputSet; + if (argc > 3) { + // The arguments of instruction pattern and the number of generating + // instructions are processed. + int32_t i = 3; + std::string argv_s(argv[i]); + if (argv_s.find("encoding=") != std::string::npos) { + char *c = argv[i]; + c += 9; + if (strcmp(c, "random") == 0) { + random_only = true; + } else { + cmdline_encoding = static_cast<uint32_t>(strtoul(c, NULL, 16)); + } + i++; + } + + if ((argc > 4) || (i == 3)) { + argv_s = std::string(argv[i]); + if (argv_s.find("input=") != std::string::npos) { + char *c = argv[i]; + c += 6; + if (strcmp(c, "fp") == 0) { + input_set = kFpInputSet; + } else { + VIXL_ASSERT(strcmp(c, "int") == 0); + } + i++; + } + } + + // Ensure all arguments have been processed. + VIXL_ASSERT(argc == i); + } + + srand48(42); + + MacroAssembler masm; + masm.GetCPUFeatures()->Combine(CPUFeatures::kSVE); + + std::map<int, Simulator *> sim_vl; + for (int i = 128; i <= 2048; i += 128) { + sim_vl[i] = new Simulator(new Decoder()); + sim_vl[i]->SetVectorLengthInBits(i); + } + + char buffer[256]; + Decoder trial_decoder; + Disassembler disasm(buffer, sizeof(buffer)); + InstructionReporter reporter; + trial_decoder.AppendVisitor(&reporter); + trial_decoder.AppendVisitor(&disasm); + + using InstrData = struct { + Instr inst; + std::string disasm; + uint32_t state_hash; + }; + std::vector<InstrData> useful_insts; + + // Seen states are only considered for vl128. It's assumed that a new state + // for vl128 implies a new state for all other vls. + std::set<uint32_t> seen_states; + uint32_t state_hash; + + std::map<int, uint32_t> initial_state_vl; + std::map<int, uint32_t> state_hash_vl; + + // Compute hash of the initial state of the machine. + Label test; + masm.Bind(&test); + masm.PushCalleeSavedRegisters(); + SetInitialMachineState(&masm, input_set); + ComputeMachineStateHash(&masm, &state_hash); + masm.PopCalleeSavedRegisters(); + masm.Ret(); + masm.FinalizeCode(); + masm.GetBuffer()->SetExecutable(); + + for (std::pair<int, Simulator *> s : sim_vl) { + s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); + initial_state_vl[s.first] = state_hash; + if (s.first == 128) seen_states.insert(state_hash); + } + + masm.GetBuffer()->SetWritable(); + masm.Reset(); + + // Count number of failed instructions, in order to allow changing instruction + // candidate strategy. + int miss_count = 0; + + while (useful_insts.size() < count) { + miss_count++; + + Instr inst; + if (cmdline_encoding != 0) { + // Initial instruction encoding supplied on the command line. + inst = cmdline_encoding; + cmdline_encoding = 0; + } else if (useful_insts.empty() || random_only || (miss_count > 10000)) { + // LCG-random instruction. + inst = static_cast<Instr>(mrand48()); + } else { + // Instruction based on mutation of last successful instruction. + inst = Mutate(useful_insts.back().inst); + } + + trial_decoder.Decode(reinterpret_cast<Instruction *>(&inst)); + if (std::regex_search(reporter.MoveForm(), std::regex(target_re))) { + // Disallow "unimplemented" instructions. + std::string buffer_s(buffer); + if (buffer_s.find("unimplemented") != std::string::npos) continue; + + // Disallow instructions with "sp" in their arguments, as we don't support + // instructions operating on memory, and the OS expects sp to be valid for + // signal handlers, etc. + size_t space = buffer_s.find(' '); + if ((space != std::string::npos) && + (buffer_s.substr(space).find("sp") != std::string::npos)) + continue; + + fprintf(stderr, "Trying 0x%08x (%s)\n", inst, buffer); + + // TODO: factorise this code into a CalculateState helper function. + + // Initialise the machine to a known state. + masm.PushCalleeSavedRegisters(); + SetInitialMachineState(&masm, input_set); + + { + ExactAssemblyScope scope(&masm, + (useful_insts.size() + 1) * kInstructionSize); + + // Emit any instructions already found to move the state to somewhere + // new. + for (const InstrData &i : useful_insts) { + masm.dci(i.inst); + } + + // Try a new instruction. + masm.dci(inst); + } + + // Compute the new state of the machine. + ComputeMachineStateHash(&masm, &state_hash); + masm.PopCalleeSavedRegisters(); + masm.Ret(); + masm.FinalizeCode(); + masm.GetBuffer()->SetExecutable(); + + // Try the new instruction for VL128. + sim_vl[128]->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); + state_hash_vl[128] = state_hash; + + if (seen_states.count(state_hash_vl[128]) == 0) { + // A new state! Run for all VLs, record it, add the instruction to the + // list of useful ones. + + for (std::pair<int, Simulator *> s : sim_vl) { + if (s.first == 128) continue; + s.second->RunFrom(masm.GetLabelAddress<Instruction *>(&test)); + state_hash_vl[s.first] = state_hash; + } + + seen_states.insert(state_hash_vl[128]); + useful_insts.push_back({inst, buffer, state_hash_vl[128]}); + miss_count = 0; + } else { + // Machine already reached here. Probably not an interesting + // instruction. NB. it's possible for an instruction to reach the same + // machine state as two or more others, but for these purposes, let's + // call that not useful. + fprintf(stderr, + "Already reached state 0x%08x, skipping 0x%08x, miss_count " + "%d\n", + state_hash_vl[128], + inst, + miss_count); + } + + // Restart generation. + masm.GetBuffer()->SetWritable(); + masm.Reset(); + } + } + + // Emit test case based on identified instructions and associated hashes. + printf("TEST_SVE(sve2_%s) {\n", target_re.c_str()); + printf( + " SVE_SETUP_WITH_FEATURES(CPUFeatures::kSVE, CPUFeatures::kSVE2, " + "CPUFeatures::kNEON, " + "CPUFeatures::kCRC32);\n"); + printf(" START();\n\n"); + printf((input_set == kFpInputSet) + ? " SetInitialMachineState(&masm, kFpInputSet);\n" + : " SetInitialMachineState(&masm);\n"); + printf(" // state = 0x%08x\n\n", initial_state_vl[128]); + + printf(" {\n"); + printf(" ExactAssemblyScope scope(&masm, %lu * kInstructionSize);\n", + useful_insts.size()); + for (InstrData &i : useful_insts) { + printf(" __ dci(0x%08x); // %s\n", i.inst, i.disasm.c_str()); + printf(" // vl128 state = 0x%08x\n", i.state_hash); + } + printf(" }\n\n"); + printf(" uint32_t state;\n"); + printf(" ComputeMachineStateHash(&masm, &state);\n"); + printf(" __ Mov(x0, reinterpret_cast<uint64_t>(&state));\n"); + printf(" __ Ldr(w0, MemOperand(x0));\n\n"); + printf(" END();\n"); + printf(" if (CAN_RUN()) {\n"); + printf(" RUN();\n"); + printf(" uint32_t expected_hashes[] = {\n"); + for (std::pair<int, uint32_t> h : state_hash_vl) { + printf(" 0x%08x,\n", h.second); + } + printf(" };\n"); + printf( + " ASSERT_EQUAL_64(expected_hashes[core.GetSVELaneCount(kQRegSize) - " + "1], x0);\n"); + printf(" }\n}\n"); + + return 0; +} +#endif |