1 // Copyright 2019, 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 #ifndef VIXL_AARCH64_BENCH_UTILS_H_ 28 #define VIXL_AARCH64_BENCH_UTILS_H_ 29 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <sys/time.h> 33 34 #include <list> 35 #include <vector> 36 37 #include "globals-vixl.h" 38 #include "aarch64/macro-assembler-aarch64.h" 39 40 class BenchTimer { 41 public: BenchTimer()42 BenchTimer() { gettimeofday(&start_, NULL); } 43 GetElapsedSeconds()44 double GetElapsedSeconds() const { 45 timeval elapsed = GetElapsed(); 46 double sec = elapsed.tv_sec; 47 double usec = elapsed.tv_usec; 48 return sec + (usec / 1000000.0); 49 } 50 HasRunFor(uint32_t seconds)51 bool HasRunFor(uint32_t seconds) { 52 timeval elapsed = GetElapsed(); 53 VIXL_ASSERT(elapsed.tv_sec >= 0); 54 return static_cast<uint64_t>(elapsed.tv_sec) >= seconds; 55 } 56 57 private: GetElapsed()58 timeval GetElapsed() const { 59 VIXL_ASSERT(timerisset(&start_)); 60 timeval now, elapsed; 61 gettimeofday(&now, NULL); 62 timersub(&now, &start_, &elapsed); 63 return elapsed; 64 } 65 66 timeval start_; 67 }; 68 69 // Provide a standard command-line interface for all benchmarks. 70 class BenchCLI { 71 public: 72 // Set default values. BenchCLI(int argc,char * argv[])73 BenchCLI(int argc, char* argv[]) 74 : run_time_(kDefaultRunTime), status_(kRunBenchmark) { 75 for (int i = 1; i < argc; i++) { 76 if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--help") == 0)) { 77 PrintUsage(argv[0]); 78 status_ = kExitSuccess; 79 return; 80 } 81 } 82 83 // Use the default run time. 84 if (argc == 1) return; 85 86 if (argc != 2) { 87 if (argc > 0) PrintUsage(argv[0]); 88 status_ = kExitFailure; 89 return; 90 } 91 92 char* end; 93 unsigned long run_time = strtoul(argv[1], &end, 0); // NOLINT(runtime/int) 94 if ((end == argv[1]) || (run_time > UINT32_MAX)) { 95 PrintUsage(argv[0]); 96 status_ = kExitFailure; 97 return; 98 } 99 run_time_ = static_cast<uint32_t>(run_time); 100 } 101 PrintUsage(char * name)102 void PrintUsage(char* name) { 103 printf("USAGE: %s [OPTIONS]... [RUN_TIME]\n", name); 104 printf("\n"); 105 printf("Run a single VIXL benchmark for approximately RUN_TIME seconds,\n"); 106 printf("or %" PRIu32 " seconds if unspecified.\n", kDefaultRunTime); 107 printf("\n"); 108 #ifdef VIXL_DEBUG 109 printf("This is a DEBUG build. VIXL's assertions will be enabled, and\n"); 110 printf("extra debug information may be printed. The benchmark results\n"); 111 printf("are not representative of expected VIXL deployments.\n"); 112 printf("\n"); 113 #endif 114 printf("OPTIONS:\n"); 115 printf("\n"); 116 printf(" -h, --help\n"); 117 printf(" Print this help message.\n"); 118 } 119 PrintResults(uint64_t iterations,double elapsed_seconds)120 void PrintResults(uint64_t iterations, double elapsed_seconds) { 121 double score = iterations / elapsed_seconds; 122 printf("%g iteration%s per second (%" PRIu64 " / %g)", 123 score, 124 (score == 1.0) ? "" : "s", 125 iterations, 126 elapsed_seconds); 127 #ifdef VIXL_DEBUG 128 printf(" [Warning: DEBUG build]"); 129 #endif 130 printf("\n"); 131 } 132 ShouldExitEarly()133 bool ShouldExitEarly() const { 134 switch (status_) { 135 case kRunBenchmark: 136 return false; 137 case kExitFailure: 138 case kExitSuccess: 139 return true; 140 } 141 VIXL_UNREACHABLE(); 142 return true; 143 } 144 GetExitCode()145 int GetExitCode() const { 146 switch (status_) { 147 case kExitFailure: 148 return EXIT_FAILURE; 149 case kExitSuccess: 150 case kRunBenchmark: 151 return EXIT_SUCCESS; 152 } 153 VIXL_UNREACHABLE(); 154 return EXIT_FAILURE; 155 } 156 GetRunTimeInSeconds()157 uint32_t GetRunTimeInSeconds() const { return run_time_; } 158 159 private: 160 static const uint32_t kDefaultRunTime = 5; 161 162 uint32_t run_time_; 163 164 enum { kRunBenchmark, kExitSuccess, kExitFailure } status_; 165 }; 166 167 // Generate random, but valid (and simulatable) instruction sequences. 168 // 169 // The effect of the generated code is meaningless, but not harmful. That is, 170 // it will not abort, callee-saved registers are properly preserved and so on. 171 // It is possible to call it as a `void fn(void)` function. 172 class BenchCodeGenerator { 173 public: BenchCodeGenerator(vixl::aarch64::MacroAssembler * masm)174 explicit BenchCodeGenerator(vixl::aarch64::MacroAssembler* masm) 175 : masm_(masm), rnd_(0), rnd_bits_(0), call_depth_(0) { 176 // Arbitrarily initialise rand_state_ using the behaviour of srand48(42). 177 rand_state_[2] = 0; 178 rand_state_[1] = 42; 179 rand_state_[0] = 0x330e; 180 } 181 182 void Generate(size_t min_size_in_bytes); 183 184 private: 185 void GeneratePrologue(); 186 void GenerateEpilogue(); 187 188 // Arbitrarily pick one of the other Generate*Sequence() functions. 189 // TODO: Consider allowing this to be biased, so that a benchmark can focus on 190 // a subset of sequences. 191 void GenerateArbitrarySequence(); 192 193 // Instructions with a trivial pass-through to Emit(). 194 void GenerateTrivialSequence(); 195 196 // Instructions using the Operand and MemOperand abstractions. These have a 197 // run-time cost, and many common VIXL APIs use them. 198 void GenerateOperandSequence(); 199 void GenerateMemOperandSequence(); 200 201 // Generate instructions taking immediates that require analysis (and may 202 // result in multiple instructions per macro). 203 void GenerateImmediateSequence(); 204 205 // Immediate-offset and register branches. This also (necessarily) covers adr. 206 void GenerateBranchSequence(); 207 208 // Generate nested, conventional (blr+ret) calls. 209 void GenerateCallReturnSequence(); 210 211 void GenerateFPSequence(); 212 void GenerateNEONSequence(); 213 214 // To exercise veneer pools, GenerateBranchSequence links labels that are 215 // expected to be bound later. This helper binds them. 216 // The Nth youngest label is bound if bit <N> is set in `bind_mask`. That 217 // means that this helper can bind at most 64 pending labels. 218 void BindPendingLabels(uint64_t bind_mask); 219 220 // As above, but unconditionally bind all pending labels (even if there are 221 // more than 64 of them). 222 void BindAllPendingLabels(); 223 224 // Argument selection helpers. These only return caller-saved registers. 225 226 uint64_t GetRandomBits(int bits); PickBool()227 bool PickBool() { return GetRandomBits(1) != 0; } 228 229 unsigned PickRSize(); 230 unsigned PickFPSize(); 231 232 vixl::aarch64::Register PickR(unsigned size_in_bits); 233 vixl::aarch64::VRegister PickV( 234 unsigned size_in_bits = vixl::aarch64::kQRegSize); 235 PickW()236 vixl::aarch64::Register PickW() { return PickR(vixl::aarch64::kWRegSize); } PickX()237 vixl::aarch64::Register PickX() { return PickR(vixl::aarch64::kXRegSize); } PickH()238 vixl::aarch64::VRegister PickH() { return PickV(vixl::aarch64::kHRegSize); } PickS()239 vixl::aarch64::VRegister PickS() { return PickV(vixl::aarch64::kSRegSize); } PickD()240 vixl::aarch64::VRegister PickD() { return PickV(vixl::aarch64::kDRegSize); } 241 242 vixl::aarch64::MacroAssembler* masm_; 243 244 // State for *rand48(), used to randomise code generation. 245 unsigned short rand_state_[3]; // NOLINT(runtime/int) 246 247 uint32_t rnd_; 248 int rnd_bits_; 249 250 // The generator can produce nested calls. The probability of it doing this is 251 // influenced by the current call depth, so we have to track it here. 252 int call_depth_; 253 254 struct LabelPair { 255 // We can't copy labels, so we have to allocate them dynamically to store 256 // them in a std::list. 257 vixl::aarch64::Label* target; 258 vixl::aarch64::Label* cont; 259 }; 260 std::list<LabelPair> labels_; 261 262 // Some sequences need a scratch area. Space for this is allocated on the 263 // stack, and stored in this register. 264 static const vixl::aarch64::Register scratch; 265 }; 266 267 #endif // VIXL_AARCH64_BENCH_UTILS_H_ 268