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