//===-- llvm/BinaryFormat/MachO.cpp - The MachO file format -----*- C++/-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/BinaryFormat/MachO.h"
#include "llvm/TargetParser/ARMTargetParser.h"
#include "llvm/TargetParser/Triple.h"

using namespace llvm;

static MachO::CPUSubTypeX86 getX86SubType(const Triple &T) {
  assert(T.isX86());
  if (T.isArch32Bit())
    return MachO::CPU_SUBTYPE_I386_ALL;

  assert(T.isArch64Bit());
  if (T.getArchName() == "x86_64h")
    return MachO::CPU_SUBTYPE_X86_64_H;
  return MachO::CPU_SUBTYPE_X86_64_ALL;
}

static MachO::CPUSubTypeARM getARMSubType(const Triple &T) {
  assert(T.isARM() || T.isThumb());
  StringRef Arch = T.getArchName();
  ARM::ArchKind AK = ARM::parseArch(Arch);
  switch (AK) {
  default:
    return MachO::CPU_SUBTYPE_ARM_V7;
  case ARM::ArchKind::ARMV4T:
    return MachO::CPU_SUBTYPE_ARM_V4T;
  case ARM::ArchKind::ARMV5T:
  case ARM::ArchKind::ARMV5TE:
  case ARM::ArchKind::ARMV5TEJ:
    return MachO::CPU_SUBTYPE_ARM_V5;
  case ARM::ArchKind::ARMV6:
  case ARM::ArchKind::ARMV6K:
    return MachO::CPU_SUBTYPE_ARM_V6;
  case ARM::ArchKind::ARMV7A:
    return MachO::CPU_SUBTYPE_ARM_V7;
  case ARM::ArchKind::ARMV7S:
    return MachO::CPU_SUBTYPE_ARM_V7S;
  case ARM::ArchKind::ARMV7K:
    return MachO::CPU_SUBTYPE_ARM_V7K;
  case ARM::ArchKind::ARMV6M:
    return MachO::CPU_SUBTYPE_ARM_V6M;
  case ARM::ArchKind::ARMV7M:
    return MachO::CPU_SUBTYPE_ARM_V7M;
  case ARM::ArchKind::ARMV7EM:
    return MachO::CPU_SUBTYPE_ARM_V7EM;
  }
}

static MachO::CPUSubTypeARM64 getARM64SubType(const Triple &T) {
  assert(T.isAArch64());
  if (T.isArch32Bit())
    return (MachO::CPUSubTypeARM64)MachO::CPU_SUBTYPE_ARM64_32_V8;
  if (T.isArm64e())
    return MachO::CPU_SUBTYPE_ARM64E;

  return MachO::CPU_SUBTYPE_ARM64_ALL;
}

static MachO::CPUSubTypePowerPC getPowerPCSubType(const Triple &T) {
  return MachO::CPU_SUBTYPE_POWERPC_ALL;
}

static Error unsupported(const char *Str, const Triple &T) {
  return createStringError(std::errc::invalid_argument,
                           "Unsupported triple for mach-o cpu %s: %s", Str,
                           T.str().c_str());
}

static MachO::CPUSubTypeRISCV getRISCVSubType(const Triple &T) {
  return MachO::CPU_SUBTYPE_RISCV_ALL;
}

Expected<uint32_t> MachO::getCPUType(const Triple &T) {
  if (!T.isOSBinFormatMachO())
    return unsupported("type", T);
  if (T.isX86() && T.isArch32Bit())
    return MachO::CPU_TYPE_X86;
  if (T.isX86() && T.isArch64Bit())
    return MachO::CPU_TYPE_X86_64;
  if (T.isARM() || T.isThumb())
    return MachO::CPU_TYPE_ARM;
  if (T.isAArch64())
    return T.isArch32Bit() ? MachO::CPU_TYPE_ARM64_32 : MachO::CPU_TYPE_ARM64;
  if (T.getArch() == Triple::ppc)
    return MachO::CPU_TYPE_POWERPC;
  if (T.getArch() == Triple::ppc64)
    return MachO::CPU_TYPE_POWERPC64;
  if (T.getArch() == Triple::riscv32)
    return MachO::CPU_TYPE_RISCV;
  return unsupported("type", T);
}

Expected<uint32_t> MachO::getCPUSubType(const Triple &T) {
  if (!T.isOSBinFormatMachO())
    return unsupported("subtype", T);
  if (T.isX86())
    return getX86SubType(T);
  if (T.isARM() || T.isThumb())
    return getARMSubType(T);
  if (T.isAArch64() || T.getArch() == Triple::aarch64_32)
    return getARM64SubType(T);
  if (T.getArch() == Triple::ppc || T.getArch() == Triple::ppc64)
    return getPowerPCSubType(T);
  if (T.getArch() == Triple::riscv32)
    return getRISCVSubType(T);
  return unsupported("subtype", T);
}

Expected<uint32_t> MachO::getCPUSubType(const Triple &T,
                                        unsigned PtrAuthABIVersion,
                                        bool PtrAuthKernelABIVersion) {
  Expected<uint32_t> Result = MachO::getCPUSubType(T);
  if (!Result)
    return Result.takeError();
  if (*Result != MachO::CPU_SUBTYPE_ARM64E)
    return createStringError(
        std::errc::invalid_argument,
        "ptrauth ABI version is only supported on arm64e.");
  if (PtrAuthABIVersion > 0xF)
    return createStringError(
        std::errc::invalid_argument,
        "The ptrauth ABI version needs to fit within 4 bits.");
  return CPU_SUBTYPE_ARM64E_WITH_PTRAUTH_VERSION(PtrAuthABIVersion,
                                                 PtrAuthKernelABIVersion);
}
