/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
#define ART_COMPILER_UTILS_ASSEMBLER_TEST_H_

#include "assembler.h"

#include <sys/stat.h>

#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iterator>

#include "base/malloc_arena_pool.h"
#include "assembler_test_base.h"
#include "common_runtime_test.h"  // For ScratchFile

namespace art {

// Helper for a constexpr string length.
constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) {
  return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1);
}

enum class RegisterView {  // private
  kUsePrimaryName,
  kUseSecondaryName,
  kUseTertiaryName,
  kUseQuaternaryName,
};

// For use in the template as the default type to get a nonvector registers version.
struct NoVectorRegs {};

template<typename Ass,
         typename Addr,
         typename Reg,
         typename FPReg,
         typename Imm,
         typename VecReg = NoVectorRegs>
class AssemblerTest : public testing::Test {
 public:
  Ass* GetAssembler() {
    return assembler_.get();
  }

  typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);

  void DriverFn(TestFn f, const std::string& test_name) {
    DriverWrapper(f(this, assembler_.get()), test_name);
  }

  // This driver assumes the assembler has already been called.
  void DriverStr(const std::string& assembly_string, const std::string& test_name) {
    DriverWrapper(assembly_string, test_name);
  }

  //
  // Register repeats.
  //

  std::string RepeatR(void (Ass::*f)(Reg), const std::string& fmt) {
    return RepeatTemplatedRegister<Reg>(f,
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string Repeatr(void (Ass::*f)(Reg), const std::string& fmt) {
    return RepeatTemplatedRegister<Reg>(f,
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        fmt);
  }

  std::string RepeatRR(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string RepeatRRNoDupes(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegistersNoDupes<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string Repeatrr(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        fmt);
  }

  std::string Repeatww(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseTertiaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUseTertiaryName>,
        fmt);
  }

  std::string Repeatbb(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
        fmt);
  }

  std::string RepeatRRR(void (Ass::*f)(Reg, Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string Repeatrb(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
        fmt);
  }

  std::string RepeatRr(void (Ass::*f)(Reg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, Reg>(f,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        fmt);
  }

  std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, const std::string& fmt) {
    return RepeatRegisterImm<RegisterView::kUsePrimaryName>(f, imm_bytes, fmt);
  }

  std::string RepeatrI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, const std::string& fmt) {
    return RepeatRegisterImm<RegisterView::kUseSecondaryName>(f, imm_bytes, fmt);
  }

  std::string RepeatwI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, const std::string& fmt) {
    return RepeatRegisterImm<RegisterView::kUseTertiaryName>(f, imm_bytes, fmt);
  }

  std::string RepeatbI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, const std::string& fmt) {
    return RepeatRegisterImm<RegisterView::kUseQuaternaryName>(f, imm_bytes, fmt);
  }

  template <typename Reg1, typename Reg2, typename ImmType>
  std::string RepeatTemplatedRegistersImmBits(void (Ass::*f)(Reg1, Reg2, ImmType),
                                              int imm_bits,
                                              const std::vector<Reg1*> reg1_registers,
                                              const std::vector<Reg2*> reg2_registers,
                                              std::string (AssemblerTest::*GetName1)(const Reg1&),
                                              std::string (AssemblerTest::*GetName2)(const Reg2&),
                                              const std::string& fmt,
                                              int bias = 0,
                                              int multiplier = 1) {
    std::string str;
    std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));

    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        for (int64_t imm : imms) {
          ImmType new_imm = CreateImmediate(imm);
          if (f != nullptr) {
            (assembler_.get()->*f)(*reg1, *reg2, new_imm * multiplier + bias);
          }
          std::string base = fmt;

          std::string reg1_string = (this->*GetName1)(*reg1);
          size_t reg1_index;
          while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
            base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
          }

          std::string reg2_string = (this->*GetName2)(*reg2);
          size_t reg2_index;
          while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
            base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
          }

          size_t imm_index = base.find(IMM_TOKEN);
          if (imm_index != std::string::npos) {
            std::ostringstream sreg;
            sreg << imm * multiplier + bias;
            std::string imm_string = sreg.str();
            base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
          }

          if (str.size() > 0) {
            str += "\n";
          }
          str += base;
        }
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename Reg1, typename Reg2, typename Reg3, typename ImmType>
  std::string RepeatTemplatedRegistersImmBits(void (Ass::*f)(Reg1, Reg2, Reg3, ImmType),
                                              int imm_bits,
                                              const std::vector<Reg1*> reg1_registers,
                                              const std::vector<Reg2*> reg2_registers,
                                              const std::vector<Reg3*> reg3_registers,
                                              std::string (AssemblerTest::*GetName1)(const Reg1&),
                                              std::string (AssemblerTest::*GetName2)(const Reg2&),
                                              std::string (AssemblerTest::*GetName3)(const Reg3&),
                                              std::string fmt,
                                              int bias) {
    std::string str;
    std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));

    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        for (auto reg3 : reg3_registers) {
          for (int64_t imm : imms) {
            ImmType new_imm = CreateImmediate(imm);
            if (f != nullptr) {
              (assembler_.get()->*f)(*reg1, *reg2, *reg3, new_imm + bias);
            }
            std::string base = fmt;

            std::string reg1_string = (this->*GetName1)(*reg1);
            size_t reg1_index;
            while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
              base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
            }

            std::string reg2_string = (this->*GetName2)(*reg2);
            size_t reg2_index;
            while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
              base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
            }

            std::string reg3_string = (this->*GetName3)(*reg3);
            size_t reg3_index;
            while ((reg3_index = base.find(REG3_TOKEN)) != std::string::npos) {
              base.replace(reg3_index, ConstexprStrLen(REG3_TOKEN), reg3_string);
            }

            size_t imm_index = base.find(IMM_TOKEN);
            if (imm_index != std::string::npos) {
              std::ostringstream sreg;
              sreg << imm + bias;
              std::string imm_string = sreg.str();
              base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
            }

            if (str.size() > 0) {
              str += "\n";
            }
            str += base;
          }
        }
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename ImmType, typename Reg1, typename Reg2>
  std::string RepeatTemplatedImmBitsRegisters(void (Ass::*f)(ImmType, Reg1, Reg2),
                                              const std::vector<Reg1*> reg1_registers,
                                              const std::vector<Reg2*> reg2_registers,
                                              std::string (AssemblerTest::*GetName1)(const Reg1&),
                                              std::string (AssemblerTest::*GetName2)(const Reg2&),
                                              int imm_bits,
                                              const std::string& fmt) {
    std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));

    WarnOnCombinations(reg1_registers.size() * reg2_registers.size() * imms.size());

    std::string str;
    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        for (int64_t imm : imms) {
          ImmType new_imm = CreateImmediate(imm);
          if (f != nullptr) {
            (assembler_.get()->*f)(new_imm, *reg1, *reg2);
          }
          std::string base = fmt;

          std::string reg1_string = (this->*GetName1)(*reg1);
          size_t reg1_index;
          while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
            base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
          }

          std::string reg2_string = (this->*GetName2)(*reg2);
          size_t reg2_index;
          while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
            base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
          }

          size_t imm_index = base.find(IMM_TOKEN);
          if (imm_index != std::string::npos) {
            std::ostringstream sreg;
            sreg << imm;
            std::string imm_string = sreg.str();
            base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
          }

          if (str.size() > 0) {
            str += "\n";
          }
          str += base;
        }
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename RegType, typename ImmType>
  std::string RepeatTemplatedRegisterImmBits(void (Ass::*f)(RegType, ImmType),
                                             int imm_bits,
                                             const std::vector<RegType*> registers,
                                             std::string (AssemblerTest::*GetName)(const RegType&),
                                             const std::string& fmt,
                                             int bias) {
    std::string str;
    std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));

    for (auto reg : registers) {
      for (int64_t imm : imms) {
        ImmType new_imm = CreateImmediate(imm);
        if (f != nullptr) {
          (assembler_.get()->*f)(*reg, new_imm + bias);
        }
        std::string base = fmt;

        std::string reg_string = (this->*GetName)(*reg);
        size_t reg_index;
        while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
          base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
        }

        size_t imm_index = base.find(IMM_TOKEN);
        if (imm_index != std::string::npos) {
          std::ostringstream sreg;
          sreg << imm + bias;
          std::string imm_string = sreg.str();
          base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename ImmType>
  std::string RepeatRRIb(void (Ass::*f)(Reg, Reg, ImmType),
                         int imm_bits,
                         const std::string& fmt,
                         int bias = 0) {
    return RepeatTemplatedRegistersImmBits<Reg, Reg, ImmType>(f,
        imm_bits,
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt,
        bias);
  }

  template <typename ImmType>
  std::string RepeatRRRIb(void (Ass::*f)(Reg, Reg, Reg, ImmType),
                          int imm_bits,
                          const std::string& fmt,
                          int bias = 0) {
    return RepeatTemplatedRegistersImmBits<Reg, Reg, Reg, ImmType>(f,
        imm_bits,
        GetRegisters(),
        GetRegisters(),
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt,
        bias);
  }

  template <typename ImmType>
  std::string RepeatRIb(void (Ass::*f)(Reg, ImmType), int imm_bits, std::string fmt, int bias = 0) {
    return RepeatTemplatedRegisterImmBits<Reg, ImmType>(f,
        imm_bits,
        GetRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt,
        bias);
  }

  template <typename ImmType>
  std::string RepeatFRIb(void (Ass::*f)(FPReg, Reg, ImmType),
                         int imm_bits,
                         const std::string& fmt,
                         int bias = 0) {
    return RepeatTemplatedRegistersImmBits<FPReg, Reg, ImmType>(f,
        imm_bits,
        GetFPRegisters(),
        GetRegisters(),
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt,
        bias);
  }

  std::string RepeatFF(void (Ass::*f)(FPReg, FPReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<FPReg, FPReg>(f,
                                                  GetFPRegisters(),
                                                  GetFPRegisters(),
                                                  &AssemblerTest::GetFPRegName,
                                                  &AssemblerTest::GetFPRegName,
                                                  fmt);
  }

  std::string RepeatFFF(void (Ass::*f)(FPReg, FPReg, FPReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<FPReg, FPReg, FPReg>(f,
                                                         GetFPRegisters(),
                                                         GetFPRegisters(),
                                                         GetFPRegisters(),
                                                         &AssemblerTest::GetFPRegName,
                                                         &AssemblerTest::GetFPRegName,
                                                         &AssemblerTest::GetFPRegName,
                                                         fmt);
  }

  std::string RepeatFFR(void (Ass::*f)(FPReg, FPReg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<FPReg, FPReg, Reg>(
        f,
        GetFPRegisters(),
        GetFPRegisters(),
        GetRegisters(),
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string RepeatFFI(void (Ass::*f)(FPReg, FPReg, const Imm&),
                        size_t imm_bytes,
                        const std::string& fmt) {
    return RepeatTemplatedRegistersImm<FPReg, FPReg>(f,
                                                     GetFPRegisters(),
                                                     GetFPRegisters(),
                                                     &AssemblerTest::GetFPRegName,
                                                     &AssemblerTest::GetFPRegName,
                                                     imm_bytes,
                                                     fmt);
  }

  template <typename ImmType>
  std::string RepeatFFIb(void (Ass::*f)(FPReg, FPReg, ImmType),
                         int imm_bits,
                         const std::string& fmt) {
    return RepeatTemplatedRegistersImmBits<FPReg, FPReg, ImmType>(f,
                                                                  imm_bits,
                                                                  GetFPRegisters(),
                                                                  GetFPRegisters(),
                                                                  &AssemblerTest::GetFPRegName,
                                                                  &AssemblerTest::GetFPRegName,
                                                                  fmt);
  }

  template <typename ImmType>
  std::string RepeatIbFF(void (Ass::*f)(ImmType, FPReg, FPReg),
                         int imm_bits,
                         const std::string& fmt) {
    return RepeatTemplatedImmBitsRegisters<ImmType, FPReg, FPReg>(f,
                                                                  GetFPRegisters(),
                                                                  GetFPRegisters(),
                                                                  &AssemblerTest::GetFPRegName,
                                                                  &AssemblerTest::GetFPRegName,
                                                                  imm_bits,
                                                                  fmt);
  }

  std::string RepeatFR(void (Ass::*f)(FPReg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<FPReg, Reg>(f,
        GetFPRegisters(),
        GetRegisters(),
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  std::string RepeatFr(void (Ass::*f)(FPReg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<FPReg, Reg>(f,
        GetFPRegisters(),
        GetRegisters(),
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        fmt);
  }

  std::string RepeatRF(void (Ass::*f)(Reg, FPReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, FPReg>(f,
        GetRegisters(),
        GetFPRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetFPRegName,
        fmt);
  }

  std::string RepeatrF(void (Ass::*f)(Reg, FPReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<Reg, FPReg>(f,
        GetRegisters(),
        GetFPRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        &AssemblerTest::GetFPRegName,
        fmt);
  }

  std::string RepeatI(void (Ass::*f)(const Imm&),
                      size_t imm_bytes,
                      const std::string& fmt,
                      bool as_uint = false) {
    std::string str;
    std::vector<int64_t> imms = CreateImmediateValues(imm_bytes, as_uint);

    WarnOnCombinations(imms.size());

    for (int64_t imm : imms) {
      Imm new_imm = CreateImmediate(imm);
      if (f != nullptr) {
        (assembler_.get()->*f)(new_imm);
      }
      std::string base = fmt;

      size_t imm_index = base.find(IMM_TOKEN);
      if (imm_index != std::string::npos) {
        std::ostringstream sreg;
        sreg << imm;
        std::string imm_string = sreg.str();
        base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
      }

      if (str.size() > 0) {
        str += "\n";
      }
      str += base;
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  std::string RepeatVV(void (Ass::*f)(VecReg, VecReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<VecReg, VecReg>(f,
                                                    GetVectorRegisters(),
                                                    GetVectorRegisters(),
                                                    &AssemblerTest::GetVecRegName,
                                                    &AssemblerTest::GetVecRegName,
                                                    fmt);
  }

  std::string RepeatVVV(void (Ass::*f)(VecReg, VecReg, VecReg), const std::string& fmt) {
    return RepeatTemplatedRegisters<VecReg, VecReg, VecReg>(f,
                                                            GetVectorRegisters(),
                                                            GetVectorRegisters(),
                                                            GetVectorRegisters(),
                                                            &AssemblerTest::GetVecRegName,
                                                            &AssemblerTest::GetVecRegName,
                                                            &AssemblerTest::GetVecRegName,
                                                            fmt);
  }

  std::string RepeatVR(void (Ass::*f)(VecReg, Reg), const std::string& fmt) {
    return RepeatTemplatedRegisters<VecReg, Reg>(
        f,
        GetVectorRegisters(),
        GetRegisters(),
        &AssemblerTest::GetVecRegName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  template <typename ImmType>
  std::string RepeatVIb(void (Ass::*f)(VecReg, ImmType),
                        int imm_bits,
                        std::string fmt,
                        int bias = 0) {
    return RepeatTemplatedRegisterImmBits<VecReg, ImmType>(f,
                                                           imm_bits,
                                                           GetVectorRegisters(),
                                                           &AssemblerTest::GetVecRegName,
                                                           fmt,
                                                           bias);
  }

  template <typename ImmType>
  std::string RepeatVRIb(void (Ass::*f)(VecReg, Reg, ImmType),
                         int imm_bits,
                         const std::string& fmt,
                         int bias = 0,
                         int multiplier = 1) {
    return RepeatTemplatedRegistersImmBits<VecReg, Reg, ImmType>(
        f,
        imm_bits,
        GetVectorRegisters(),
        GetRegisters(),
        &AssemblerTest::GetVecRegName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt,
        bias,
        multiplier);
  }

  template <typename ImmType>
  std::string RepeatRVIb(void (Ass::*f)(Reg, VecReg, ImmType),
                         int imm_bits,
                         const std::string& fmt,
                         int bias = 0,
                         int multiplier = 1) {
    return RepeatTemplatedRegistersImmBits<Reg, VecReg, ImmType>(
        f,
        imm_bits,
        GetRegisters(),
        GetVectorRegisters(),
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetVecRegName,
        fmt,
        bias,
        multiplier);
  }

  template <typename ImmType>
  std::string RepeatVVIb(void (Ass::*f)(VecReg, VecReg, ImmType),
                         int imm_bits,
                         const std::string& fmt,
                         int bias = 0) {
    return RepeatTemplatedRegistersImmBits<VecReg, VecReg, ImmType>(f,
                                                                    imm_bits,
                                                                    GetVectorRegisters(),
                                                                    GetVectorRegisters(),
                                                                    &AssemblerTest::GetVecRegName,
                                                                    &AssemblerTest::GetVecRegName,
                                                                    fmt,
                                                                    bias);
  }

  // This is intended to be run as a test.
  bool CheckTools() {
    return test_helper_->CheckTools();
  }

  // The following functions are public so that TestFn can use them...

  // Returns a vector of address used by any of the repeat methods
  // involving an "A" (e.g. RepeatA).
  virtual std::vector<Addr> GetAddresses() = 0;

  // Returns a vector of registers used by any of the repeat methods
  // involving an "R" (e.g. RepeatR).
  virtual std::vector<Reg*> GetRegisters() = 0;

  // Returns a vector of fp-registers used by any of the repeat methods
  // involving an "F" (e.g. RepeatFF).
  virtual std::vector<FPReg*> GetFPRegisters() {
    UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
    UNREACHABLE();
  }

  // Returns a vector of dedicated simd-registers used by any of the repeat
  // methods involving an "V" (e.g. RepeatVV).
  virtual std::vector<VecReg*> GetVectorRegisters() {
    UNIMPLEMENTED(FATAL) << "Architecture does not support vector registers";
    UNREACHABLE();
  }

  // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems.
  virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
    UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers";
    UNREACHABLE();
  }

  // Tertiary register names are the tertiary view on registers, e.g., 16b on 64b systems.
  virtual std::string GetTertiaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
    UNIMPLEMENTED(FATAL) << "Architecture does not support tertiary registers";
    UNREACHABLE();
  }

  // Quaternary register names are the quaternary view on registers, e.g., 8b on 64b systems.
  virtual std::string GetQuaternaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) {
    UNIMPLEMENTED(FATAL) << "Architecture does not support quaternary registers";
    UNREACHABLE();
  }

  std::string GetRegisterName(const Reg& reg) {
    return GetRegName<RegisterView::kUsePrimaryName>(reg);
  }

 protected:
  AssemblerTest() {}

  void SetUp() override {
    allocator_.reset(new ArenaAllocator(&pool_));
    assembler_.reset(CreateAssembler(allocator_.get()));
    test_helper_.reset(
        new AssemblerTestInfrastructure(GetArchitectureString(),
                                        GetAssemblerCmdName(),
                                        GetAssemblerParameters(),
                                        GetObjdumpCmdName(),
                                        GetObjdumpParameters(),
                                        GetDisassembleCmdName(),
                                        GetDisassembleParameters(),
                                        GetAssemblyHeader()));

    SetUpHelpers();
  }

  void TearDown() override {
    test_helper_.reset();  // Clean up the helper.
    assembler_.reset();
    allocator_.reset();
  }

  // Override this to set up any architecture-specific things, e.g., CPU revision.
  virtual Ass* CreateAssembler(ArenaAllocator* allocator) {
    return new (allocator) Ass(allocator);
  }

  // Override this to set up any architecture-specific things, e.g., register vectors.
  virtual void SetUpHelpers() {}

  // Get the typically used name for this architecture, e.g., aarch64, x86_64, ...
  virtual std::string GetArchitectureString() = 0;

  // Get the name of the assembler, e.g., "as" by default.
  virtual std::string GetAssemblerCmdName() {
    return "as";
  }

  // Switches to the assembler command. Default none.
  virtual std::string GetAssemblerParameters() {
    return "";
  }

  // Get the name of the objdump, e.g., "objdump" by default.
  virtual std::string GetObjdumpCmdName() {
    return "objdump";
  }

  // Switches to the objdump command. Default is " -h".
  virtual std::string GetObjdumpParameters() {
    return " -h";
  }

  // Get the name of the objdump, e.g., "objdump" by default.
  virtual std::string GetDisassembleCmdName() {
    return "objdump";
  }

  // Switches to the objdump command. As it's a binary, one needs to push the architecture and
  // such to objdump, so it's architecture-specific and there is no default.
  virtual std::string GetDisassembleParameters() = 0;

  // Create a couple of immediate values up to the number of bytes given.
  virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes, bool as_uint = false) {
    std::vector<int64_t> res;
    res.push_back(0);
    if (!as_uint) {
      res.push_back(-1);
    } else {
      res.push_back(0xFF);
    }
    res.push_back(0x12);
    if (imm_bytes >= 2) {
      res.push_back(0x1234);
      if (!as_uint) {
        res.push_back(-0x1234);
      } else {
        res.push_back(0xFFFF);
      }
      if (imm_bytes >= 4) {
        res.push_back(0x12345678);
        if (!as_uint) {
          res.push_back(-0x12345678);
        } else {
          res.push_back(0xFFFFFFFF);
        }
        if (imm_bytes >= 6) {
          res.push_back(0x123456789ABC);
          if (!as_uint) {
            res.push_back(-0x123456789ABC);
          }
          if (imm_bytes >= 8) {
            res.push_back(0x123456789ABCDEF0);
            if (!as_uint) {
              res.push_back(-0x123456789ABCDEF0);
            } else {
              res.push_back(0xFFFFFFFFFFFFFFFF);
            }
          }
        }
      }
    }
    return res;
  }

  const int kMaxBitsExhaustiveTest = 8;

  // Create a couple of immediate values up to the number of bits given.
  virtual std::vector<int64_t> CreateImmediateValuesBits(const int imm_bits, bool as_uint = false) {
    CHECK_GT(imm_bits, 0);
    CHECK_LE(imm_bits, 64);
    std::vector<int64_t> res;

    if (imm_bits <= kMaxBitsExhaustiveTest) {
      if (as_uint) {
        for (uint64_t i = MinInt<uint64_t>(imm_bits); i <= MaxInt<uint64_t>(imm_bits); i++) {
          res.push_back(static_cast<int64_t>(i));
        }
      } else {
        for (int64_t i = MinInt<int64_t>(imm_bits); i <= MaxInt<int64_t>(imm_bits); i++) {
          res.push_back(i);
        }
      }
    } else {
      if (as_uint) {
        for (uint64_t i = MinInt<uint64_t>(kMaxBitsExhaustiveTest);
             i <= MaxInt<uint64_t>(kMaxBitsExhaustiveTest);
             i++) {
          res.push_back(static_cast<int64_t>(i));
        }
        for (int i = 0; i <= imm_bits; i++) {
          uint64_t j = (MaxInt<uint64_t>(kMaxBitsExhaustiveTest) + 1) +
                       ((MaxInt<uint64_t>(imm_bits) -
                        (MaxInt<uint64_t>(kMaxBitsExhaustiveTest) + 1))
                        * i / imm_bits);
          res.push_back(static_cast<int64_t>(j));
        }
      } else {
        for (int i = 0; i <= imm_bits; i++) {
          int64_t j = MinInt<int64_t>(imm_bits) +
                      ((((MinInt<int64_t>(kMaxBitsExhaustiveTest) - 1) -
                         MinInt<int64_t>(imm_bits))
                        * i) / imm_bits);
          res.push_back(static_cast<int64_t>(j));
        }
        for (int64_t i = MinInt<int64_t>(kMaxBitsExhaustiveTest);
             i <= MaxInt<int64_t>(kMaxBitsExhaustiveTest);
             i++) {
          res.push_back(static_cast<int64_t>(i));
        }
        for (int i = 0; i <= imm_bits; i++) {
          int64_t j = (MaxInt<int64_t>(kMaxBitsExhaustiveTest) + 1) +
                      ((MaxInt<int64_t>(imm_bits) - (MaxInt<int64_t>(kMaxBitsExhaustiveTest) + 1))
                       * i / imm_bits);
          res.push_back(static_cast<int64_t>(j));
        }
      }
    }

    return res;
  }

  // Create an immediate from the specific value.
  virtual Imm CreateImmediate(int64_t imm_value) = 0;

  //
  // Addresses repeats.
  //

  // Repeats over addresses provided by fixture.
  std::string RepeatA(void (Ass::*f)(const Addr&), const std::string& fmt) {
    return RepeatA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatA(void (Ass::*f)(const Addr&),
                      const std::vector<Addr>& a,
                      const std::string& fmt) {
    return RepeatTemplatedMem<Addr>(f, a, &AssemblerTest::GetAddrName, fmt);
  }

  // Repeats over addresses and immediates provided by fixture.
  std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&),
                       size_t imm_bytes,
                       const std::string& fmt) {
    return RepeatAI(f, imm_bytes, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&),
                       size_t imm_bytes,
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemImm<Addr>(f, imm_bytes, a, &AssemblerTest::GetAddrName, fmt);
  }

  // Repeats over registers and addresses provided by fixture.
  std::string RepeatRA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) {
    return RepeatRA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatRA(void (Ass::*f)(Reg, const Addr&),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedRegMem<Reg, Addr>(
        f,
        GetRegisters(),
        a,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        &AssemblerTest::GetAddrName,
        fmt);
  }

  // Repeats over secondary registers and addresses provided by fixture.
  std::string RepeatrA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) {
    return RepeatrA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatrA(void (Ass::*f)(Reg, const Addr&),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedRegMem<Reg, Addr>(
        f,
        GetRegisters(),
        a,
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        &AssemblerTest::GetAddrName,
        fmt);
  }

  // Repeats over tertiary registers and addresses provided by fixture.
  std::string RepeatwA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) {
    return RepeatwA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatwA(void (Ass::*f)(Reg, const Addr&),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedRegMem<Reg, Addr>(
        f,
        GetRegisters(),
        a,
        &AssemblerTest::GetRegName<RegisterView::kUseTertiaryName>,
        &AssemblerTest::GetAddrName,
        fmt);
  }

  // Repeats over quaternary registers and addresses provided by fixture.
  std::string RepeatbA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) {
    return RepeatbA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatbA(void (Ass::*f)(Reg, const Addr&),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedRegMem<Reg, Addr>(
        f,
        GetRegisters(),
        a,
        &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
        &AssemblerTest::GetAddrName,
        fmt);
  }

  // Repeats over fp-registers and addresses provided by fixture.
  std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&), const std::string& fmt) {
    return RepeatFA(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedRegMem<FPReg, Addr>(
        f,
        GetFPRegisters(),
        a,
        &AssemblerTest::GetFPRegName,
        &AssemblerTest::GetAddrName,
        fmt);
  }

  // Repeats over addresses and registers provided by fixture.
  std::string RepeatAR(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) {
    return RepeatAR(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAR(void (Ass::*f)(const Addr&, Reg),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemReg<Addr, Reg>(
        f,
        a,
        GetRegisters(),
        &AssemblerTest::GetAddrName,
        &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
        fmt);
  }

  // Repeats over addresses and secondary registers provided by fixture.
  std::string RepeatAr(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) {
    return RepeatAr(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAr(void (Ass::*f)(const Addr&, Reg),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemReg<Addr, Reg>(
        f,
        a,
        GetRegisters(),
        &AssemblerTest::GetAddrName,
        &AssemblerTest::GetRegName<RegisterView::kUseSecondaryName>,
        fmt);
  }

  // Repeats over addresses and tertiary registers provided by fixture.
  std::string RepeatAw(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) {
    return RepeatAw(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAw(void (Ass::*f)(const Addr&, Reg),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemReg<Addr, Reg>(
        f,
        a,
        GetRegisters(),
        &AssemblerTest::GetAddrName,
        &AssemblerTest::GetRegName<RegisterView::kUseTertiaryName>,
        fmt);
  }

  // Repeats over addresses and quaternary registers provided by fixture.
  std::string RepeatAb(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) {
    return RepeatAb(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAb(void (Ass::*f)(const Addr&, Reg),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemReg<Addr, Reg>(
        f,
        a,
        GetRegisters(),
        &AssemblerTest::GetAddrName,
        &AssemblerTest::GetRegName<RegisterView::kUseQuaternaryName>,
        fmt);
  }

  // Repeats over addresses and fp-registers provided by fixture.
  std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg), const std::string& fmt) {
    return RepeatAF(f, GetAddresses(), fmt);
  }

  // Variant that takes explicit vector of addresss
  // (to test restricted addressing modes set).
  std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg),
                       const std::vector<Addr>& a,
                       const std::string& fmt) {
    return RepeatTemplatedMemReg<Addr, FPReg>(
        f,
        a,
        GetFPRegisters(),
        &AssemblerTest::GetAddrName,
        &AssemblerTest::GetFPRegName,
        fmt);
  }

  template <typename AddrType>
  std::string RepeatTemplatedMem(void (Ass::*f)(const AddrType&),
                                 const std::vector<AddrType> addresses,
                                 std::string (AssemblerTest::*GetAName)(const AddrType&),
                                 const std::string& fmt) {
    WarnOnCombinations(addresses.size());
    std::string str;
    for (auto addr : addresses) {
      if (f != nullptr) {
        (assembler_.get()->*f)(addr);
      }
      std::string base = fmt;

      std::string addr_string = (this->*GetAName)(addr);
      size_t addr_index;
      if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
        base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
      }

      if (str.size() > 0) {
        str += "\n";
      }
      str += base;
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename AddrType>
  std::string RepeatTemplatedMemImm(void (Ass::*f)(const AddrType&, const Imm&),
                                    size_t imm_bytes,
                                    const std::vector<AddrType> addresses,
                                    std::string (AssemblerTest::*GetAName)(const AddrType&),
                                    const std::string& fmt) {
    std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
    WarnOnCombinations(addresses.size() * imms.size());
    std::string str;
    for (auto addr : addresses) {
      for (int64_t imm : imms) {
        Imm new_imm = CreateImmediate(imm);
        if (f != nullptr) {
          (assembler_.get()->*f)(addr, new_imm);
        }
        std::string base = fmt;

        std::string addr_string = (this->*GetAName)(addr);
        size_t addr_index;
        if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
          base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
        }

        size_t imm_index = base.find(IMM_TOKEN);
        if (imm_index != std::string::npos) {
          std::ostringstream sreg;
          sreg << imm;
          std::string imm_string = sreg.str();
          base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename RegType, typename AddrType>
  std::string RepeatTemplatedRegMem(void (Ass::*f)(RegType, const AddrType&),
                                    const std::vector<RegType*> registers,
                                    const std::vector<AddrType> addresses,
                                    std::string (AssemblerTest::*GetRName)(const RegType&),
                                    std::string (AssemblerTest::*GetAName)(const AddrType&),
                                    const std::string& fmt) {
    WarnOnCombinations(addresses.size() * registers.size());
    std::string str;
    for (auto reg : registers) {
      for (auto addr : addresses) {
        if (f != nullptr) {
          (assembler_.get()->*f)(*reg, addr);
        }
        std::string base = fmt;

        std::string reg_string = (this->*GetRName)(*reg);
        size_t reg_index;
        if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
          base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
        }

        std::string addr_string = (this->*GetAName)(addr);
        size_t addr_index;
        if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
          base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename AddrType, typename RegType>
  std::string RepeatTemplatedMemReg(void (Ass::*f)(const AddrType&, RegType),
                                    const std::vector<AddrType> addresses,
                                    const std::vector<RegType*> registers,
                                    std::string (AssemblerTest::*GetAName)(const AddrType&),
                                    std::string (AssemblerTest::*GetRName)(const RegType&),
                                    const std::string& fmt) {
    WarnOnCombinations(addresses.size() * registers.size());
    std::string str;
    for (auto addr : addresses) {
      for (auto reg : registers) {
        if (f != nullptr) {
          (assembler_.get()->*f)(addr, *reg);
        }
        std::string base = fmt;

        std::string addr_string = (this->*GetAName)(addr);
        size_t addr_index;
        if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
          base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
        }

        std::string reg_string = (this->*GetRName)(*reg);
        size_t reg_index;
        if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
          base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  //
  // Register repeats.
  //

  template <typename RegType>
  std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
                                      const std::vector<RegType*> registers,
                                      std::string (AssemblerTest::*GetName)(const RegType&),
                                      const std::string& fmt) {
    std::string str;
    for (auto reg : registers) {
      if (f != nullptr) {
        (assembler_.get()->*f)(*reg);
      }
      std::string base = fmt;

      std::string reg_string = (this->*GetName)(*reg);
      size_t reg_index;
      if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
        base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
      }

      if (str.size() > 0) {
        str += "\n";
      }
      str += base;
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename Reg1, typename Reg2>
  std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2),
                                       const std::vector<Reg1*> reg1_registers,
                                       const std::vector<Reg2*> reg2_registers,
                                       std::string (AssemblerTest::*GetName1)(const Reg1&),
                                       std::string (AssemblerTest::*GetName2)(const Reg2&),
                                       const std::string& fmt) {
    WarnOnCombinations(reg1_registers.size() * reg2_registers.size());

    std::string str;
    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        if (f != nullptr) {
          (assembler_.get()->*f)(*reg1, *reg2);
        }
        std::string base = fmt;

        std::string reg1_string = (this->*GetName1)(*reg1);
        size_t reg1_index;
        while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
          base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
        }

        std::string reg2_string = (this->*GetName2)(*reg2);
        size_t reg2_index;
        while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
          base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename Reg1, typename Reg2>
  std::string RepeatTemplatedRegistersNoDupes(void (Ass::*f)(Reg1, Reg2),
                                              const std::vector<Reg1*> reg1_registers,
                                              const std::vector<Reg2*> reg2_registers,
                                              std::string (AssemblerTest::*GetName1)(const Reg1&),
                                              std::string (AssemblerTest::*GetName2)(const Reg2&),
                                              const std::string& fmt) {
    WarnOnCombinations(reg1_registers.size() * reg2_registers.size());

    std::string str;
    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        if (reg1 == reg2) continue;
        if (f != nullptr) {
          (assembler_.get()->*f)(*reg1, *reg2);
        }
        std::string base = fmt;

        std::string reg1_string = (this->*GetName1)(*reg1);
        size_t reg1_index;
        while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
          base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
        }

        std::string reg2_string = (this->*GetName2)(*reg2);
        size_t reg2_index;
        while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
          base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename Reg1, typename Reg2, typename Reg3>
  std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2, Reg3),
                                       const std::vector<Reg1*> reg1_registers,
                                       const std::vector<Reg2*> reg2_registers,
                                       const std::vector<Reg3*> reg3_registers,
                                       std::string (AssemblerTest::*GetName1)(const Reg1&),
                                       std::string (AssemblerTest::*GetName2)(const Reg2&),
                                       std::string (AssemblerTest::*GetName3)(const Reg3&),
                                       const std::string& fmt) {
    std::string str;
    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        for (auto reg3 : reg3_registers) {
          if (f != nullptr) {
            (assembler_.get()->*f)(*reg1, *reg2, *reg3);
          }
          std::string base = fmt;

          std::string reg1_string = (this->*GetName1)(*reg1);
          size_t reg1_index;
          while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
            base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
          }

          std::string reg2_string = (this->*GetName2)(*reg2);
          size_t reg2_index;
          while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
            base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
          }

          std::string reg3_string = (this->*GetName3)(*reg3);
          size_t reg3_index;
          while ((reg3_index = base.find(REG3_TOKEN)) != std::string::npos) {
            base.replace(reg3_index, ConstexprStrLen(REG3_TOKEN), reg3_string);
          }

          if (str.size() > 0) {
            str += "\n";
          }
          str += base;
        }
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  template <typename Reg1, typename Reg2>
  std::string RepeatTemplatedRegistersImm(void (Ass::*f)(Reg1, Reg2, const Imm&),
                                          const std::vector<Reg1*> reg1_registers,
                                          const std::vector<Reg2*> reg2_registers,
                                          std::string (AssemblerTest::*GetName1)(const Reg1&),
                                          std::string (AssemblerTest::*GetName2)(const Reg2&),
                                          size_t imm_bytes,
                                          const std::string& fmt) {
    std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
    WarnOnCombinations(reg1_registers.size() * reg2_registers.size() * imms.size());

    std::string str;
    for (auto reg1 : reg1_registers) {
      for (auto reg2 : reg2_registers) {
        for (int64_t imm : imms) {
          Imm new_imm = CreateImmediate(imm);
          if (f != nullptr) {
            (assembler_.get()->*f)(*reg1, *reg2, new_imm);
          }
          std::string base = fmt;

          std::string reg1_string = (this->*GetName1)(*reg1);
          size_t reg1_index;
          while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) {
            base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string);
          }

          std::string reg2_string = (this->*GetName2)(*reg2);
          size_t reg2_index;
          while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) {
            base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string);
          }

          size_t imm_index = base.find(IMM_TOKEN);
          if (imm_index != std::string::npos) {
            std::ostringstream sreg;
            sreg << imm;
            std::string imm_string = sreg.str();
            base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
          }

          if (str.size() > 0) {
            str += "\n";
          }
          str += base;
        }
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  std::string GetAddrName(const Addr& addr) {
    std::ostringstream saddr;
    saddr << addr;
    return saddr.str();
  }

  template <RegisterView kRegView>
  std::string GetRegName(const Reg& reg) {
    std::ostringstream sreg;
    switch (kRegView) {
      case RegisterView::kUsePrimaryName:
        sreg << reg;
        break;

      case RegisterView::kUseSecondaryName:
        sreg << GetSecondaryRegisterName(reg);
        break;

      case RegisterView::kUseTertiaryName:
        sreg << GetTertiaryRegisterName(reg);
        break;

      case RegisterView::kUseQuaternaryName:
        sreg << GetQuaternaryRegisterName(reg);
        break;
    }
    return sreg.str();
  }

  std::string GetFPRegName(const FPReg& reg) {
    std::ostringstream sreg;
    sreg << reg;
    return sreg.str();
  }

  std::string GetVecRegName(const VecReg& reg) {
    std::ostringstream sreg;
    sreg << reg;
    return sreg.str();
  }

  // If the assembly file needs a header, return it in a sub-class.
  virtual const char* GetAssemblyHeader() {
    return nullptr;
  }

  void WarnOnCombinations(size_t count) {
    if (count > kWarnManyCombinationsThreshold) {
      GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow.";
    }
  }

  static constexpr const char* ADDRESS_TOKEN = "{mem}";
  static constexpr const char* REG_TOKEN = "{reg}";
  static constexpr const char* REG1_TOKEN = "{reg1}";
  static constexpr const char* REG2_TOKEN = "{reg2}";
  static constexpr const char* REG3_TOKEN = "{reg3}";
  static constexpr const char* IMM_TOKEN = "{imm}";

 private:
  template <RegisterView kRegView>
  std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&),
                                size_t imm_bytes,
                                const std::string& fmt) {
    const std::vector<Reg*> registers = GetRegisters();
    std::string str;
    std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);

    WarnOnCombinations(registers.size() * imms.size());

    for (auto reg : registers) {
      for (int64_t imm : imms) {
        Imm new_imm = CreateImmediate(imm);
        if (f != nullptr) {
          (assembler_.get()->*f)(*reg, new_imm);
        }
        std::string base = fmt;

        std::string reg_string = GetRegName<kRegView>(*reg);
        size_t reg_index;
        while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
          base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
        }

        size_t imm_index = base.find(IMM_TOKEN);
        if (imm_index != std::string::npos) {
          std::ostringstream sreg;
          sreg << imm;
          std::string imm_string = sreg.str();
          base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
        }

        if (str.size() > 0) {
          str += "\n";
        }
        str += base;
      }
    }
    // Add a newline at the end.
    str += "\n";
    return str;
  }

  // Override this to pad the code with NOPs to a certain size if needed.
  virtual void Pad(std::vector<uint8_t>& data ATTRIBUTE_UNUSED) {
  }

  void DriverWrapper(const std::string& assembly_text, const std::string& test_name) {
    assembler_->FinalizeCode();
    size_t cs = assembler_->CodeSize();
    std::unique_ptr<std::vector<uint8_t>> data(new std::vector<uint8_t>(cs));
    MemoryRegion code(&(*data)[0], data->size());
    assembler_->FinalizeInstructions(code);
    Pad(*data);
    test_helper_->Driver(*data, assembly_text, test_name);
  }

  static constexpr size_t kWarnManyCombinationsThreshold = 500;

  MallocArenaPool pool_;
  std::unique_ptr<ArenaAllocator> allocator_;
  std::unique_ptr<Ass> assembler_;
  std::unique_ptr<AssemblerTestInfrastructure> test_helper_;

  DISALLOW_COPY_AND_ASSIGN(AssemblerTest);
};

}  // namespace art

#endif  // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_
