| // Copyright 2019, 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. |
| |
| #ifndef VIXL_AARCH64_BENCH_UTILS_H_ |
| #define VIXL_AARCH64_BENCH_UTILS_H_ |
| |
| #include <list> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| #include <vector> |
| |
| #include "globals-vixl.h" |
| |
| #include "aarch64/macro-assembler-aarch64.h" |
| |
| class BenchTimer { |
| public: |
| BenchTimer() { gettimeofday(&start_, NULL); } |
| |
| double GetElapsedSeconds() const { |
| timeval elapsed = GetElapsed(); |
| double sec = elapsed.tv_sec; |
| double usec = elapsed.tv_usec; |
| return sec + (usec / 1000000.0); |
| } |
| |
| bool HasRunFor(uint32_t seconds) { |
| timeval elapsed = GetElapsed(); |
| VIXL_ASSERT(elapsed.tv_sec >= 0); |
| return static_cast<uint64_t>(elapsed.tv_sec) >= seconds; |
| } |
| |
| private: |
| timeval GetElapsed() const { |
| VIXL_ASSERT(timerisset(&start_)); |
| timeval now, elapsed; |
| gettimeofday(&now, NULL); |
| timersub(&now, &start_, &elapsed); |
| return elapsed; |
| } |
| |
| timeval start_; |
| }; |
| |
| // Provide a standard command-line interface for all benchmarks. |
| class BenchCLI { |
| public: |
| // Set default values. |
| BenchCLI(int argc, char* argv[]) |
| : run_time_(kDefaultRunTime), status_(kRunBenchmark) { |
| for (int i = 1; i < argc; i++) { |
| if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--help") == 0)) { |
| PrintUsage(argv[0]); |
| status_ = kExitSuccess; |
| return; |
| } |
| } |
| |
| // Use the default run time. |
| if (argc == 1) return; |
| |
| if (argc != 2) { |
| if (argc > 0) PrintUsage(argv[0]); |
| status_ = kExitFailure; |
| return; |
| } |
| |
| char* end; |
| unsigned long run_time = // NOLINT(google-runtime-int) |
| strtoul(argv[1], &end, 0); |
| if ((end == argv[1]) || (run_time > UINT32_MAX)) { |
| PrintUsage(argv[0]); |
| status_ = kExitFailure; |
| return; |
| } |
| run_time_ = static_cast<uint32_t>(run_time); |
| } |
| |
| void PrintUsage(char* name) { |
| printf("USAGE: %s [OPTIONS]... [RUN_TIME]\n", name); |
| printf("\n"); |
| printf("Run a single VIXL benchmark for approximately RUN_TIME seconds,\n"); |
| printf("or %" PRIu32 " seconds if unspecified.\n", kDefaultRunTime); |
| printf("\n"); |
| #ifdef VIXL_DEBUG |
| printf("This is a DEBUG build. VIXL's assertions will be enabled, and\n"); |
| printf("extra debug information may be printed. The benchmark results\n"); |
| printf("are not representative of expected VIXL deployments.\n"); |
| printf("\n"); |
| #endif |
| printf("OPTIONS:\n"); |
| printf("\n"); |
| printf(" -h, --help\n"); |
| printf(" Print this help message.\n"); |
| } |
| |
| void PrintResults(uint64_t iterations, double elapsed_seconds) { |
| double score = iterations / elapsed_seconds; |
| printf("%g iteration%s per second (%" PRIu64 " / %g)", |
| score, |
| (score == 1.0) ? "" : "s", |
| iterations, |
| elapsed_seconds); |
| #ifdef VIXL_DEBUG |
| printf(" [Warning: DEBUG build]"); |
| #endif |
| printf("\n"); |
| } |
| |
| bool ShouldExitEarly() const { |
| switch (status_) { |
| case kRunBenchmark: |
| return false; |
| case kExitFailure: |
| case kExitSuccess: |
| return true; |
| } |
| VIXL_UNREACHABLE(); |
| return true; |
| } |
| |
| int GetExitCode() const { |
| switch (status_) { |
| case kExitFailure: |
| return EXIT_FAILURE; |
| case kExitSuccess: |
| case kRunBenchmark: |
| return EXIT_SUCCESS; |
| } |
| VIXL_UNREACHABLE(); |
| return EXIT_FAILURE; |
| } |
| |
| uint32_t GetRunTimeInSeconds() const { return run_time_; } |
| |
| private: |
| static const uint32_t kDefaultRunTime = 5; |
| |
| uint32_t run_time_; |
| |
| enum { kRunBenchmark, kExitSuccess, kExitFailure } status_; |
| }; |
| |
| // Generate random, but valid (and simulatable) instruction sequences. |
| // |
| // The effect of the generated code is meaningless, but not harmful. That is, |
| // it will not abort, callee-saved registers are properly preserved and so on. |
| // It is possible to call it as a `void fn(void)` function. |
| class BenchCodeGenerator { |
| public: |
| explicit BenchCodeGenerator(vixl::aarch64::MacroAssembler* masm) |
| : masm_(masm), rnd_(0), rnd_bits_(0), call_depth_(0) { |
| // Arbitrarily initialise rand_state_ using the behaviour of srand48(42). |
| rand_state_[2] = 0; |
| rand_state_[1] = 42; |
| rand_state_[0] = 0x330e; |
| } |
| |
| void Generate(size_t min_size_in_bytes); |
| |
| private: |
| void GeneratePrologue(); |
| void GenerateEpilogue(); |
| |
| // Arbitrarily pick one of the other Generate*Sequence() functions. |
| // TODO: Consider allowing this to be biased, so that a benchmark can focus on |
| // a subset of sequences. |
| void GenerateArbitrarySequence(); |
| |
| // Instructions with a trivial pass-through to Emit(). |
| void GenerateTrivialSequence(); |
| |
| // Instructions using the Operand and MemOperand abstractions. These have a |
| // run-time cost, and many common VIXL APIs use them. |
| void GenerateOperandSequence(); |
| void GenerateMemOperandSequence(); |
| |
| // Generate instructions taking immediates that require analysis (and may |
| // result in multiple instructions per macro). |
| void GenerateImmediateSequence(); |
| |
| // Immediate-offset and register branches. This also (necessarily) covers adr. |
| void GenerateBranchSequence(); |
| |
| // Generate nested, conventional (blr+ret) calls. |
| void GenerateCallReturnSequence(); |
| |
| void GenerateFPSequence(); |
| void GenerateNEONSequence(); |
| |
| // To exercise veneer pools, GenerateBranchSequence links labels that are |
| // expected to be bound later. This helper binds them. |
| // The Nth youngest label is bound if bit <N> is set in `bind_mask`. That |
| // means that this helper can bind at most 64 pending labels. |
| void BindPendingLabels(uint64_t bind_mask); |
| |
| // As above, but unconditionally bind all pending labels (even if there are |
| // more than 64 of them). |
| void BindAllPendingLabels(); |
| |
| // Argument selection helpers. These only return caller-saved registers. |
| |
| uint64_t GetRandomBits(int bits); |
| bool PickBool() { return GetRandomBits(1) != 0; } |
| |
| unsigned PickRSize(); |
| unsigned PickFPSize(); |
| |
| vixl::aarch64::Register PickR(unsigned size_in_bits); |
| vixl::aarch64::VRegister PickV( |
| unsigned size_in_bits = vixl::aarch64::kQRegSize); |
| |
| vixl::aarch64::Register PickW() { return PickR(vixl::aarch64::kWRegSize); } |
| vixl::aarch64::Register PickX() { return PickR(vixl::aarch64::kXRegSize); } |
| vixl::aarch64::VRegister PickH() { return PickV(vixl::aarch64::kHRegSize); } |
| vixl::aarch64::VRegister PickS() { return PickV(vixl::aarch64::kSRegSize); } |
| vixl::aarch64::VRegister PickD() { return PickV(vixl::aarch64::kDRegSize); } |
| |
| vixl::aarch64::MacroAssembler* masm_; |
| |
| // State for *rand48(), used to randomise code generation. |
| unsigned short rand_state_[3]; // NOLINT(google-runtime-int) |
| |
| uint32_t rnd_; |
| int rnd_bits_; |
| |
| // The generator can produce nested calls. The probability of it doing this is |
| // influenced by the current call depth, so we have to track it here. |
| int call_depth_; |
| |
| struct LabelPair { |
| // We can't copy labels, so we have to allocate them dynamically to store |
| // them in a std::list. |
| vixl::aarch64::Label* target; |
| vixl::aarch64::Label* cont; |
| }; |
| std::list<LabelPair> labels_; |
| |
| // Some sequences need a scratch area. Space for this is allocated on the |
| // stack, and stored in this register. |
| static const vixl::aarch64::Register scratch; |
| }; |
| |
| #endif // VIXL_AARCH64_BENCH_UTILS_H_ |