blob: 59da38c40bfa824bff80446c0cd01b216f11731b [file] [log] [blame]
// Copyright 2024 The IREE Authors
//
// Licensed 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 "compiler/plugins/target/LLVMCPU/ResolveCPUAndCPUFeatures.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TargetParser/AArch64TargetParser.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/RISCVTargetParser.h"
#include "llvm/TargetParser/SubtargetFeature.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/TargetParser/X86TargetParser.h"
namespace mlir::iree_compiler::IREE::HAL {
namespace {
ResolveCPUAndCPUFeaturesStatus
resolveHostCPUAndCPUFeatures(std::string &cpu, std::string &cpuFeatures) {
if (cpu != "host" && cpuFeatures != "host") {
return ResolveCPUAndCPUFeaturesStatus::OK;
}
if ((!cpu.empty() && cpu != "host") ||
(!cpuFeatures.empty() && cpuFeatures != "host")) {
return ResolveCPUAndCPUFeaturesStatus::InconsistentHost;
}
cpu = llvm::sys::getHostCPUName();
llvm::SubtargetFeatures features;
for (auto &feature : llvm::sys::getHostCPUFeatures()) {
features.AddFeature(feature.first(), feature.second);
}
cpuFeatures = features.getString();
return ResolveCPUAndCPUFeaturesStatus::OK;
}
ResolveCPUAndCPUFeaturesStatus
resolveCPUFeaturesForCPU(const llvm::Triple &triple, std::string &cpu,
std::string &cpuFeatures) {
if (!cpuFeatures.empty()) {
// Explicitly specified CPU features: not overriding.
return ResolveCPUAndCPUFeaturesStatus::OK;
}
if (cpu.empty() || cpu == "generic" ||
llvm::StringRef(cpu).starts_with("generic-")) {
// Implicitly (default) or explicitly specified generic CPU: no features.
// Logging (on unspecified CPU) was already handled, no need for it here.
return ResolveCPUAndCPUFeaturesStatus::OK;
}
llvm::SubtargetFeatures targetCpuFeatures(cpuFeatures);
if (triple.isX86()) {
if (!llvm::X86::parseArchX86(cpu, /*Only64Bit=*/true)) {
return ResolveCPUAndCPUFeaturesStatus::UnknownCPU;
}
llvm::SmallVector<llvm::StringRef> features;
llvm::X86::getFeaturesForCPU(cpu, features, /*NeedPlus=*/false);
for (const auto &feature : features) {
targetCpuFeatures.AddFeature(feature);
}
} else if (triple.isRISCV64()) {
if (!llvm::RISCV::parseCPU(cpu, triple.isRISCV64())) {
return ResolveCPUAndCPUFeaturesStatus::UnknownCPU;
}
llvm::SmallVector<std::string> features;
llvm::RISCV::getFeaturesForCPU(cpu, features, /*NeedPlus=*/false);
for (const auto &feature : features) {
targetCpuFeatures.AddFeature(feature);
}
} else if (triple.isAArch64()) {
std::optional<llvm::AArch64::CpuInfo> cpuInfo =
llvm::AArch64::parseCpu(cpu);
if (!cpuInfo) {
return ResolveCPUAndCPUFeaturesStatus::UnknownCPU;
}
llvm::AArch64::ExtensionSet extensions;
extensions.addCPUDefaults(*cpuInfo);
std::vector<std::string> features;
extensions.toLLVMFeatureList(features);
for (const auto &feature : features) {
targetCpuFeatures.AddFeature(feature);
}
} else {
return ResolveCPUAndCPUFeaturesStatus::UnimplementedMapping;
}
cpuFeatures = targetCpuFeatures.getString();
return ResolveCPUAndCPUFeaturesStatus::OK;
}
void tweakCPUFeatures(const llvm::Triple &triple, std::string &cpu,
std::string &cpuFeatures) {
if (triple.isAArch64()) {
llvm::SubtargetFeatures targetCpuFeatures(cpuFeatures);
// x18 is platform-reserved per the Aarch64 procedure call specification.
targetCpuFeatures.AddFeature("reserve-x18", true);
cpuFeatures = targetCpuFeatures.getString();
}
}
std::string getImplicitGenericFallbackMessage(std::string_view triple_str) {
llvm::StringRef triple_str_ref(triple_str);
llvm::Triple triple(triple_str_ref);
std::string msg = R"MSG(
Defaulting to targeting a generic CPU for the target architecture will result in poor performance. Please specify a target CPU and/or a target CPU feature set. If it is intended to target a generic CPU, specify "generic" as the CPU.
This can be done in two ways:
1. With command-line flags:
--iree-llvmcpu-target-cpu=...
--iree-llvmcpu-target-cpu-features=...
2. Within the IR:
#hal.executable.target< ... , cpu="...", cpu_features="...">
In the rest of this message, these fields are referred to as just `cpu` and `cpu_features`.
Examples:
cpu=generic
Target a generic CPU of the target architecture. The generated code will have poor performance, but will run on any CPU.
cpu=host
Target the host CPU. The generated code will have optimal performance on the host CPU but will crash on other CPUs not supporting the same CPU features.
cpu="name"
Target a specific CPU. This is mostly used on x86. The accepted values are the same as in Clang command lines.)MSG";
if (triple.isX86()) {
msg += R"MSG(
List of accepted x86 CPUs: )MSG";
llvm::SmallVector<llvm::StringRef> allAcceptedCpus;
llvm::X86::fillValidCPUArchList(allAcceptedCpus, /*Only64Bit=*/true);
llvm::raw_string_ostream s(msg);
llvm::interleaveComma(allAcceptedCpus, s);
msg += "\n";
} else {
msg += R"MSG(
CAVEAT: Outside of x86, this may only set the instruction scheduling model but may not enable CPU features. That's why when targeting non-x86 CPUs, it is usually preferred to pass cpu_features, see below.
)MSG";
}
msg += R"MSG(
cpu_features="+feature1,..."
Target a CPU supporting the comma-separated of (+-prefixed) features. The accepted values are the same as in Clang command lines.
)MSG";
if (triple.isAArch64()) {
msg += R"MSG(
Example: cpu_features="+dotprod,+i8mm,+bf16
)MSG";
}
if (triple.isRISCV()) {
msg += R"MSG(
Example: cpu_features="+m,+a,+f,+d,+c
)MSG";
}
return msg;
}
std::string getUnknownCPUMessage(std::string_view triple_str) {
llvm::StringRef triple_str_ref(triple_str);
llvm::Triple triple(triple_str_ref);
std::string msg = llvm::formatv("Unknown CPU for target architecture {}.\n",
triple.getArchName());
if (triple.isX86()) {
msg += "List of accepted CPUs: ";
llvm::SmallVector<llvm::StringRef> allAcceptedCpus;
llvm::X86::fillValidCPUArchList(allAcceptedCpus, /*Only64Bit=*/true);
llvm::raw_string_ostream s(msg);
llvm::interleaveComma(allAcceptedCpus, s);
msg += "\n";
} else if (triple.isRISCV()) {
msg += "List of accepted CPUs: ";
llvm::SmallVector<llvm::StringRef> allAcceptedCpus;
llvm::RISCV::fillValidCPUArchList(allAcceptedCpus, /*IsRV64=*/true);
llvm::raw_string_ostream s(msg);
llvm::interleaveComma(allAcceptedCpus, s);
msg += "\n";
} else if (triple.isAArch64()) {
msg += "List of accepted CPUs: ";
llvm::SmallVector<llvm::StringRef> allAcceptedCpus;
llvm::AArch64::fillValidCPUArchList(allAcceptedCpus);
llvm::raw_string_ostream s(msg);
llvm::interleaveComma(allAcceptedCpus, s);
msg += "\n";
}
return msg;
}
} // namespace
ResolveCPUAndCPUFeaturesStatus
resolveCPUAndCPUFeatures(std::string_view triple_str, std::string &cpu,
std::string &cpuFeatures) {
// Helper to return the first non-OK status.
auto combine = [](ResolveCPUAndCPUFeaturesStatus a,
ResolveCPUAndCPUFeaturesStatus b) {
return a == ResolveCPUAndCPUFeaturesStatus::OK ? b : a;
};
llvm::StringRef triple_str_ref(triple_str);
llvm::Triple triple(triple_str_ref);
// No early-return on error status. The caller may treat these errors as
// non-fatal and will carry on with whichever `cpu` and `cpuFeatures` we
// produce.
auto status1 = resolveHostCPUAndCPUFeatures(cpu, cpuFeatures);
auto status2 = resolveCPUFeaturesForCPU(triple, cpu, cpuFeatures);
auto status12 = combine(status1, status2);
if (status12 == ResolveCPUAndCPUFeaturesStatus::OK) {
tweakCPUFeatures(triple, cpu, cpuFeatures);
}
std::string defaultTweakedCpu;
std::string defaultTweakedCpuFeatures;
tweakCPUFeatures(triple, defaultTweakedCpu, defaultTweakedCpuFeatures);
auto status3 =
(cpu == defaultTweakedCpu && cpuFeatures == defaultTweakedCpuFeatures)
? ResolveCPUAndCPUFeaturesStatus::ImplicitGenericFallback
: ResolveCPUAndCPUFeaturesStatus::OK;
return combine(status12, status3);
}
std::string getMessage(ResolveCPUAndCPUFeaturesStatus status,
std::string_view triple_str) {
switch (status) {
case ResolveCPUAndCPUFeaturesStatus::ImplicitGenericFallback:
return getImplicitGenericFallbackMessage(triple_str);
case ResolveCPUAndCPUFeaturesStatus::InconsistentHost:
return "If either CPU or CPU-features is `host`, the other must "
"be either also `host` or the default value.\n";
case ResolveCPUAndCPUFeaturesStatus::UnimplementedMapping:
return "Resolution of CPU to CPU-features is not implemented on this "
"target architecture. Pass explicit "
"CPU-features, or implement the missing mapping.\n";
case ResolveCPUAndCPUFeaturesStatus::UnknownCPU:
return getUnknownCPUMessage(triple_str);
default:
assert(false);
return "";
}
}
} // namespace mlir::iree_compiler::IREE::HAL