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