blob: 552b0b56214642f373df5b7bd81cfdc07ae85823 [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/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);
auto addCpuFeatures = [&](const auto &getFeaturesForCPU,
auto &cpuFeatureList) {
getFeaturesForCPU(cpu, cpuFeatureList, false);
for (const auto &feature : cpuFeatureList) {
targetCpuFeatures.AddFeature(feature);
}
};
if (triple.isX86()) {
llvm::SmallVector<llvm::StringRef> cpuFeatureList;
addCpuFeatures(llvm::X86::getFeaturesForCPU, cpuFeatureList);
} else if (triple.isRISCV64()) {
llvm::SmallVector<std::string> cpuFeatureList;
addCpuFeatures(llvm::RISCV::getFeaturesForCPU, cpuFeatureList);
} 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);
// Helper to add a feature if not already present. This check matters as
// we check for equality of features to tell whether to generate the error
// about implicitly targeting a generic CPU.
auto addFeature = [&](const char *feature) {
if (!targetCpuFeatures.hasFlag(std::string("+") + feature)) {
targetCpuFeatures.AddFeature(feature, true);
}
};
// x18 is platform-reserved per the Aarch64 procedure call specification.
addFeature("reserve-x18");
cpuFeatures = targetCpuFeatures.getString();
}
}
std::string getImplicitGenericFallbackMessage(std::string_view triple_str) {
llvm::Triple triple(triple_str);
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;
}
} // namespace
ResolveCPUAndCPUFeaturesStatus
resolveCPUAndCPUFeatures(std::string_view triple_str, std::string &cpu,
std::string &cpuFeatures) {
llvm::Triple triple(triple_str);
// 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);
tweakCPUFeatures(triple, cpu, cpuFeatures);
std::string defaultTweakedCpu;
std::string defaultTweakedCpuFeatures;
tweakCPUFeatures(triple, defaultTweakedCpu, defaultTweakedCpuFeatures);
auto status3 =
(cpu == defaultTweakedCpu && cpuFeatures == defaultTweakedCpuFeatures)
? ResolveCPUAndCPUFeaturesStatus::ImplicitGenericFallback
: ResolveCPUAndCPUFeaturesStatus::OK;
// Helper to return the first non-OK status.
auto combine = [](ResolveCPUAndCPUFeaturesStatus a,
ResolveCPUAndCPUFeaturesStatus b) {
return a == ResolveCPUAndCPUFeaturesStatus::OK ? b : a;
};
return combine(combine(status1, status2), 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";
default:
assert(false);
return "";
}
}
} // namespace mlir::iree_compiler::IREE::HAL