This change adds two builtins for AMDGPU: - `__builtin_amdgcn_processor_is`, which is similar in observable behaviour with `__builtin_cpu_is`, except that it is never "evaluated" at run time; - `__builtin_amdgcn_is_invocable`, which is behaviourally similar with `__has_builtin`, except that it is not a macro (i.e. not evaluated at preprocessing time). Neither of these are `constexpr`, even though when compiling for concrete (i.e. `gfxXXX` / `gfxXXX-generic`) targets they get evaluated in Clang, so they shouldn't tear the AST too badly / at all for multi-pass compilation cases like HIP. They can only be used in specific contexts (as args to control structures). The motivation for adding these is two-fold: - as a nice to have, it provides an AST-visible way to incorporate architecture specific code, rather than having to rely on macros and the preprocessor, which burn in the choice quite early; - as a must have, it allows featureful AMDGCN flavoured SPIR-V to be produced, where target specific capability is guarded and chosen or discarded when finalising compilation for a concrete target; this is built atop the Speciali\ation Constant concept which is described in the SPIR-V specification under section [2.12 Specialization](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_specialization_2) I've tried to keep the overall footprint of the change small. The changes to Sema are a bit unpleasant, but there was a strong desire to have Clang validate these, and to constrain their uses, and this was the most compact solution I could come up with (suggestions welcome). --------- Co-authored-by: Juan Manuel Martinez Caamaño <jmartinezcaamao@gmail.com> Co-authored-by: Voicu <avoicu@amd.com>
161 lines
4.9 KiB
C++
161 lines
4.9 KiB
C++
//===-- SPIRVPrepareGlobals.cpp - Prepare IR SPIRV globals ------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The pass:
|
|
// - transforms IR globals that cannot be trivially mapped to SPIRV into
|
|
// something that is trival to lower;
|
|
// - for AMDGCN flavoured SPIRV, it assigns unique IDs to the specialisation
|
|
// constants associated with feature predicates, which were inserted by the
|
|
// FE when expanding calls to __builtin_amdgcn_processor_is or
|
|
// __builtin_amdgcn_is_invocable
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SPIRV.h"
|
|
#include "SPIRVUtils.h"
|
|
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/IR/IntrinsicsSPIRV.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#include <climits>
|
|
#include <string>
|
|
|
|
#define DEBUG_TYPE "spirv-prepare-globals"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
struct SPIRVPrepareGlobals : public ModulePass {
|
|
static char ID;
|
|
SPIRVPrepareGlobals() : ModulePass(ID) {}
|
|
|
|
StringRef getPassName() const override {
|
|
return "SPIRV prepare global variables";
|
|
}
|
|
|
|
bool runOnModule(Module &M) override;
|
|
};
|
|
|
|
// The backend does not support GlobalAlias. Replace aliases with their aliasees
|
|
// when possible and remove them from the module.
|
|
bool tryReplaceAliasWithAliasee(GlobalAlias &GA) {
|
|
// According to the lang ref, aliases cannot be replaced if either the alias
|
|
// or the aliasee are interposable. We only replace in the case that both
|
|
// are not interposable.
|
|
if (GA.isInterposable()) {
|
|
LLVM_DEBUG(dbgs() << "Skipping interposable alias: " << GA.getName()
|
|
<< "\n");
|
|
return false;
|
|
}
|
|
|
|
auto *AO = dyn_cast<GlobalObject>(GA.getAliasee());
|
|
if (!AO) {
|
|
LLVM_DEBUG(dbgs() << "Skipping alias whose aliasee is not a GlobalObject: "
|
|
<< GA.getName() << "\n");
|
|
return false;
|
|
}
|
|
|
|
if (AO->isInterposable()) {
|
|
LLVM_DEBUG(dbgs() << "Skipping interposable aliasee: " << AO->getName()
|
|
<< "\n");
|
|
return false;
|
|
}
|
|
|
|
LLVM_DEBUG(dbgs() << "Replacing alias " << GA.getName()
|
|
<< " with aliasee: " << AO->getName() << "\n");
|
|
|
|
GA.replaceAllUsesWith(AO);
|
|
if (GA.isDiscardableIfUnused()) {
|
|
GA.eraseFromParent();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool tryAssignPredicateSpecConstIDs(Module &M, Function *F) {
|
|
StringMap<unsigned> IDs;
|
|
for (auto &&U : F->users()) {
|
|
auto *CI = dyn_cast<CallInst>(U);
|
|
if (!CI)
|
|
continue;
|
|
|
|
auto *SpecID = dyn_cast<ConstantInt>(CI->getArgOperand(0));
|
|
if (!SpecID)
|
|
continue;
|
|
|
|
unsigned ID = SpecID->getZExtValue();
|
|
if (ID != UINT32_MAX)
|
|
continue;
|
|
|
|
// Replace placeholder Specialisation Constant IDs with unique IDs
|
|
// associated with the predicate being evaluated, which is encoded via
|
|
// spv_assign_name.
|
|
auto *MD =
|
|
cast<MDNode>(cast<MetadataAsValue>(CI->getOperand(2))->getMetadata());
|
|
auto *P = cast<MDString>(MD->getOperand(0));
|
|
|
|
ID = IDs.try_emplace(P->getString(), IDs.size()).first->second;
|
|
CI->setArgOperand(0, ConstantInt::get(CI->getArgOperand(0)->getType(), ID));
|
|
}
|
|
|
|
if (IDs.empty())
|
|
return false;
|
|
|
|
// Store the predicate -> ID mapping as a fixed format string
|
|
// (predicate ID\0...), for later use during SPIR-V consumption.
|
|
std::string Tmp;
|
|
for (auto &&[Predicate, SpecID] : IDs)
|
|
Tmp.append(Predicate).append(" ").append(utostr(SpecID)).push_back('\0');
|
|
|
|
Constant *PredSpecIDStr =
|
|
ConstantDataArray::getString(M.getContext(), Tmp, false);
|
|
|
|
new GlobalVariable(M, PredSpecIDStr->getType(), true,
|
|
GlobalVariable::LinkageTypes::ExternalLinkage,
|
|
PredSpecIDStr, "llvm.amdgcn.feature.predicate.ids");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SPIRVPrepareGlobals::runOnModule(Module &M) {
|
|
bool Changed = false;
|
|
|
|
for (GlobalAlias &GA : make_early_inc_range(M.aliases())) {
|
|
Changed |= tryReplaceAliasWithAliasee(GA);
|
|
}
|
|
|
|
if (M.getTargetTriple().getVendor() != Triple::AMD)
|
|
return Changed;
|
|
|
|
// TODO: Currently, for AMDGCN flavoured SPIR-V, the symbol can only be
|
|
// inserted via feature predicate use, but in the future this will need
|
|
// revisiting if we start making more liberal use of the intrinsic.
|
|
if (Function *F = Intrinsic::getDeclarationIfExists(
|
|
&M, Intrinsic::spv_named_boolean_spec_constant))
|
|
Changed |= tryAssignPredicateSpecConstIDs(M, F);
|
|
|
|
return Changed;
|
|
}
|
|
char SPIRVPrepareGlobals::ID = 0;
|
|
|
|
} // namespace
|
|
|
|
INITIALIZE_PASS(SPIRVPrepareGlobals, "prepare-globals",
|
|
"SPIRV prepare global variables", false, false)
|
|
|
|
namespace llvm {
|
|
ModulePass *createSPIRVPrepareGlobalsPass() {
|
|
return new SPIRVPrepareGlobals();
|
|
}
|
|
} // namespace llvm
|