Files
UnrealEngine/Engine/Source/ThirdParty/Intel/ISPC/ispc-1.21.0/src/target_registry.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

332 lines
12 KiB
C++

/*
Copyright (c) 2019-2023, Intel Corporation
SPDX-License-Identifier: BSD-3-Clause
*/
/** @file target_registry.h
@brief Registry to handle bitcode libraries.
*/
#include "target_registry.h"
#include "util.h"
#include <numeric>
#include <string>
#include <vector>
using namespace ispc;
// Returns number of bits required to store this value
static constexpr uint32_t bits_required(uint32_t x) {
int msb_position = 0;
while (x > 0) {
x = x >> 1;
msb_position++;
}
return msb_position;
}
class Triple {
// Encode values |0000|arch|os|target|
static constexpr uint32_t target_width = bits_required((uint32_t)ISPCTarget::error);
static constexpr uint32_t os_width = bits_required((uint32_t)TargetOS::error);
static constexpr uint32_t arch_width = bits_required((uint32_t)Arch::error);
static_assert(target_width + os_width + arch_width <= 32, "Too large value to encode");
static constexpr uint32_t target_mask = (1 << target_width) - 1;
static constexpr uint32_t os_mask = ((1 << os_width) - 1) << target_width;
static constexpr uint32_t arch_mask = ((1 << arch_width) - 1) << (target_width + os_width);
public:
ISPCTarget m_target;
TargetOS m_os;
Arch m_arch;
Triple(uint32_t encoding) {
m_target = (ISPCTarget)(encoding & target_mask);
m_os = (TargetOS)((encoding & os_mask) >> target_width);
m_arch = (Arch)((encoding & arch_mask) >> (target_width + os_width));
};
Triple(ISPCTarget target, TargetOS os, Arch arch) : m_target(target), m_os(os), m_arch(arch){};
uint32_t encode() const {
uint32_t result = (uint32_t)m_arch;
result = (result << os_width) + (uint32_t)m_os;
result = (result << target_width) + (uint32_t)m_target;
return result;
};
};
std::vector<const BitcodeLib *> *TargetLibRegistry::libs = nullptr;
TargetLibRegistry::TargetLibRegistry() {
// TODO: sort before adding - to canonicalize.
// TODO: check for conflicts / duplicates.
m_dispatch = nullptr;
m_dispatch_macos = nullptr;
for (auto lib : *libs) {
switch (lib->getType()) {
case BitcodeLib::BitcodeLibType::Dispatch:
if (lib->getOS() == TargetOS::macos) {
m_dispatch_macos = lib;
} else {
m_dispatch = lib;
}
break;
case BitcodeLib::BitcodeLibType::Builtins_c:
m_builtins[Triple(lib->getISPCTarget(), lib->getOS(), lib->getArch()).encode()] = lib;
m_supported_oses[(int)lib->getOS()] = true;
// "custom_linux" target is regular "linux" target for ARM with a few tweaks.
// So, create it as an alias.
if (lib->getOS() == TargetOS::linux && (lib->getArch() == Arch::arm || lib->getArch() == Arch::aarch64)) {
m_builtins[Triple(lib->getISPCTarget(), TargetOS::custom_linux, lib->getArch()).encode()] = lib;
m_supported_oses[(int)TargetOS::custom_linux] = true;
}
// PS5 is an alias to PS4 in terms of target files. All the tuning is done through CPU flags.
if (lib->getOS() == TargetOS::ps4) {
m_builtins[Triple(lib->getISPCTarget(), TargetOS::ps5, lib->getArch()).encode()] = lib;
m_supported_oses[(int)TargetOS::ps5] = true;
}
break;
case BitcodeLib::BitcodeLibType::ISPC_target:
m_targets[Triple(lib->getISPCTarget(), lib->getOS(), lib->getArch()).encode()] = lib;
// "custom_linux" target is regular "linux" target for ARM with a few tweaks.
// So, create it as an alias.
if (lib->getOS() == TargetOS::linux && (lib->getArch() == Arch::arm || lib->getArch() == Arch::aarch64)) {
m_targets[Triple(lib->getISPCTarget(), TargetOS::custom_linux, lib->getArch()).encode()] = lib;
}
// PS5 is an alias to PS4 in terms of target files. All the tuning is done through CPU flags.
if (lib->getOS() == TargetOS::ps4) {
m_builtins[Triple(lib->getISPCTarget(), TargetOS::ps5, lib->getArch()).encode()] = lib;
}
break;
}
}
}
void TargetLibRegistry::RegisterTarget(const BitcodeLib *lib) {
if (!libs) {
libs = new std::vector<const BitcodeLib *>();
}
libs->push_back(lib);
}
TargetLibRegistry *TargetLibRegistry::getTargetLibRegistry() {
static TargetLibRegistry *reg = new TargetLibRegistry();
return reg;
}
const BitcodeLib *TargetLibRegistry::getDispatchLib(const TargetOS os) const {
return (os == TargetOS::macos) ? m_dispatch_macos : m_dispatch;
}
const BitcodeLib *TargetLibRegistry::getBuiltinsCLib(TargetOS os, Arch arch) const {
auto result = m_builtins.find(Triple(ISPCTarget::none, os, arch).encode());
if (result != m_builtins.end()) {
return result->second;
}
return nullptr;
}
const BitcodeLib *TargetLibRegistry::getISPCTargetLib(ISPCTarget target, TargetOS os, Arch arch) const {
// TODO: validate parameters not to be errors or forbidden values.
// This is an alias. It might be a good idea generalize this.
if (target == ISPCTarget::avx1_i32x4) {
target = ISPCTarget::sse4_i32x4;
}
// sse41 is an alias for sse4
switch (target) {
case ISPCTarget::sse41_i8x16:
target = ISPCTarget::sse4_i8x16;
break;
case ISPCTarget::sse41_i16x8:
target = ISPCTarget::sse4_i16x8;
break;
case ISPCTarget::sse41_i32x4:
target = ISPCTarget::sse4_i32x4;
break;
case ISPCTarget::sse41_i32x8:
target = ISPCTarget::sse4_i32x8;
break;
default:
// Fall through
;
}
// There's no Mac that supports SPR, so the decision is not support these targets when targeting macOS.
// If these targets are linked in, then we still can use them for cross compilation, for example for Linux.
if (os == TargetOS::macos && (target == ISPCTarget::avx512spr_x4 || target == ISPCTarget::avx512spr_x8 ||
target == ISPCTarget::avx512spr_x16 || target == ISPCTarget::avx512spr_x32 ||
target == ISPCTarget::avx512spr_x64)) {
return nullptr;
}
// Canonicalize OS, as for the target we only differentiate between Windows, Unix, and Web (WASM target).
switch (os) {
case TargetOS::windows:
case TargetOS::web:
// Keep these values.
break;
case TargetOS::linux:
case TargetOS::custom_linux:
case TargetOS::freebsd:
case TargetOS::macos:
case TargetOS::android:
case TargetOS::ios:
case TargetOS::ps4:
case TargetOS::ps5:
os = TargetOS::linux;
break;
case TargetOS::error:
UNREACHABLE();
}
auto result = m_targets.find(Triple(target, os, arch).encode());
if (result != m_targets.end()) {
return result->second;
}
return nullptr;
}
// Print user-friendly message about supported targets
void TargetLibRegistry::printSupportMatrix() const {
// Vector of rows, which are vectors of cells.
std::vector<std::vector<std::string>> table;
// OS names row
std::vector<std::string> os_names;
os_names.push_back("");
for (int j = (int)TargetOS::windows; j < (int)TargetOS::error; j++) {
os_names.push_back(OSToString((TargetOS)j));
}
table.push_back(os_names);
// Fill in the name, one target per the row.
for (int i = (int)ISPCTarget::sse2_i32x4; i < (int)ISPCTarget::error; i++) {
std::vector<std::string> row;
ISPCTarget target = (ISPCTarget)i;
row.push_back(ISPCTargetToString(target));
std::vector<std::string> arch_list_target;
// Fill in cell: list of arches for the target/os.
for (int j = (int)TargetOS::windows; j < (int)TargetOS::error; j++) {
std::string arch_list_os;
TargetOS os = (TargetOS)j;
for (int k = (int)Arch::none; k < (int)Arch::error; k++) {
Arch arch = (Arch)k;
if (isSupported(target, os, arch)) {
if (!arch_list_os.empty()) {
arch_list_os += ", ";
}
arch_list_os += ArchToString(arch);
}
}
arch_list_target.push_back(arch_list_os);
row.push_back(arch_list_os);
}
table.push_back(row);
}
// Collect maximum sizes for all columns
std::vector<int> column_sizes(table[0].size(), 7);
for (auto &row : table) {
for (int i = 0; i < row.size(); i++) {
column_sizes[i] = column_sizes[i] > row[i].size() ? column_sizes[i] : row[i].size();
}
}
int width = std::accumulate(column_sizes.begin(), column_sizes.end(), 0) + (column_sizes.size() - 1) * 3;
// Print the table
for (int i = 0; i < table.size(); i++) {
auto row = table[i];
for (int j = 0; j < row.size(); j++) {
auto align = std::string(column_sizes[j] - row[j].size(), ' ');
printf("%s%s", row[j].c_str(), align.c_str());
if (j + 1 != row.size()) {
printf(" | ");
}
}
printf("\n");
if (i == 0) {
auto line = std::string(width, '-');
printf("%s\n", line.c_str());
}
}
}
std::string TargetLibRegistry::getSupportedArchs() {
std::string archs;
for (int k = (int)Arch::none; k < (int)Arch::error; k++) {
Arch arch = (Arch)k;
for (int i = (int)ISPCTarget::sse2_i32x4; i < (int)ISPCTarget::error; i++) {
ISPCTarget target = (ISPCTarget)i;
for (int j = (int)TargetOS::windows; j < (int)TargetOS::error; j++) {
TargetOS os = (TargetOS)j;
if (isSupported(target, os, arch)) {
if (!archs.empty()) {
archs += ", ";
}
archs += ArchToString(arch);
goto next_arch;
}
}
}
next_arch:;
}
return archs;
}
std::string TargetLibRegistry::getSupportedTargets() {
std::string targets;
for (int i = (int)ISPCTarget::sse2_i32x4; i < (int)ISPCTarget::error; i++) {
ISPCTarget target = (ISPCTarget)i;
for (int j = (int)TargetOS::windows; j < (int)TargetOS::error; j++) {
TargetOS os = (TargetOS)j;
for (int k = (int)Arch::none; k < (int)Arch::error; k++) {
Arch arch = (Arch)k;
if (isSupported(target, os, arch)) {
if (!targets.empty()) {
targets += ", ";
}
targets += ISPCTargetToString(target);
goto next_target;
}
}
}
next_target:;
}
return targets;
}
std::string TargetLibRegistry::getSupportedOSes() {
// We use pre-computed bitset, as this function is perfomance critical - it's used
// during arguments parsing.
std::string oses;
for (int j = (int)TargetOS::windows; j < (int)TargetOS::error; j++) {
TargetOS os = (TargetOS)j;
if (m_supported_oses[j]) {
if (!oses.empty()) {
oses += ", ";
}
oses += OSToLowerString(os);
}
}
return oses;
}
bool TargetLibRegistry::isSupported(ISPCTarget target, TargetOS os, Arch arch) const {
auto clib = getBuiltinsCLib(os, arch);
if (clib) {
auto lib = getISPCTargetLib(target, os, arch);
if (lib) {
return true;
}
}
return false;
}