/* 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 #include #include 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 *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(); } 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> table; // OS names row std::vector 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 row; ISPCTarget target = (ISPCTarget)i; row.push_back(ISPCTargetToString(target)); std::vector 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 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; }