| // 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 |