/
redgpu
/
clspv
Обзор
Документация
Войти
/
redgpu
/
clspv
Код
Запросы
0
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
lib/UBOTypeTransformPass.cpp
559 строк
19 KB
alan-baker
Update LLVM (#639)
09 сен 2020, 20:44
Не верифицирован
09 сен 2020, 20:44
5a8c3be
Код
Авторство
О чём код?
// Copyright 2018 The Clspv Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This pass performs type mutation to create types that satisfy the standard // Uniform buffer layout rules of Vulkan section 14.5.4. // // Assumes the following passes have run: // UndoGetElementPtrConstantExprPass #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/CallingConv.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Function.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Module.h" #include "llvm/Pass.h" #include "llvm/Support/raw_ostream.h" #include "clspv/AddressSpace.h" #include "clspv/Option.h" #include "ArgKind.h" #include "Constants.h" #include "Passes.h" using namespace llvm; namespace { class UBOTypeTransformPass final : public ModulePass { public: static char ID; UBOTypeTransformPass() : ModulePass(ID) {} bool runOnModule(Module &M) override; private: // Returns the remapped version of |type| that satisfies UBO requirements. // |rewrite| indicates whether the type can be rewritten or just rebuilt. Type *MapType(Type *type, Module &M, bool rewrite); Type *RebuildType(Type *type, Module &M) { return MapType(type, M, false); } // Returns the remapped version of |type| that satisfies UBO requirements. // |rewrite| indicates whether the type can be rewritten or just rebuilt. StructType *MapStructType(StructType *struct_ty, Module &M, bool rewrite); // Performs type mutation on |M|. Returns true if |M| was modified. bool RemapTypes(Module &M); // Performs type mutation for functions that require it. Returns true if the // module is modified. // // If a function requires type mutation it will be replaced by a new // function. The function's basic blocks are moved into the new function and // all metadata is copied. bool RemapFunctions(SmallVectorImpl<Function *> *functions_to_modify, Module &M); // Rebuilds global variables if their types require transformation. Returns // true if the module is modified. bool RemapGlobalVariables(SmallVectorImpl<GlobalVariable *> *variables_to_modify, Module &M); // Performs type mutation on |user|. Recursively fixes operands of |user|. // Returns true if the module is modified. bool RemapUser(User *user, Module &M); // Mutates the type of |value|. Returns true if the module is modified. bool RemapValue(Value *value, Module &M); // Maps and rebuilds |constant| to match its mapped type. Returns true if the // module if modified. bool RemapConstant(Constant *constant, Module &M); // Rebuild |constant| as a constant with |remapped_ty| type. Returns the // rebuilt constant. Constant *RebuildConstant(Constant *constant, Type *remapped_ty, Module &M); // Performs final modifications on functions that were replaced. Fixes names // and use-def chains. void FixupFunctions(const ArrayRef<Function *> &functions_to_modify, Module &M); // Replaces and deletes modified global variables. void FixupGlobalVariables(const ArrayRef<GlobalVariable *> &variables_to_modify); // Maps a type to its UBO type. DenseMap<Type *, Type *> remapped_types_; // Prevents infinite recusion. DenseSet<Type *> deferred_types_; // Maps a function to its replacement. DenseMap<Function *, Function *> function_replacements_; // Maps a global value to its replacement. DenseMap<Constant *, Constant *> remapped_globals_; // Whether char arrays are supported in UBOs. bool support_int8_array_; }; } // namespace char UBOTypeTransformPass::ID = 0; INITIALIZE_PASS(UBOTypeTransformPass, "UBOTypeTransformPass", "Transform UBO types", false, false) namespace clspv { ModulePass *createUBOTypeTransformPass() { return new UBOTypeTransformPass(); } } // namespace clspv namespace { bool UBOTypeTransformPass::runOnModule(Module &M) { // Record whether char arrays are supported. support_int8_array_ = clspv::Option::Int8Support() && clspv::Option::Std430UniformBufferLayout(); bool changed = false; for (auto &F : M) { if (F.isDeclaration() || F.getCallingConv() != CallingConv::SPIR_KERNEL) continue; auto pod_arg_impl = clspv::GetPodArgsImpl(F); for (auto &Arg : F.args()) { if ((clspv::Option::ConstantArgsInUniformBuffer() && clspv::GetArgKind(Arg) == clspv::ArgKind::BufferUBO) || (!Arg.getType()->isPointerTy() && pod_arg_impl == clspv::PodArgImpl::kUBO)) { // Pre-populate the type mapping for types that must change. This // necessary to prevent caching what would appear to be a no-op too // early. MapType(Arg.getType(), M, /* rewrite = */ true); } } } if (!remapped_types_.empty()) { changed |= RemapTypes(M); } return changed; } Type *UBOTypeTransformPass::MapType(Type *type, Module &M, bool rewrite) { // Check the cache to see if we've fixed this type. auto iter = remapped_types_.find(type); if (iter != remapped_types_.end()) { return iter->second; } // Fix circular references. if (!deferred_types_.insert(type).second) { return type; } // Rebuild the types. Most types do not need handled here. Type *remapped = type; switch (type->getTypeID()) { case Type::PointerTyID: { PointerType *pointer = cast<PointerType>(type); Type *pointee = MapType(pointer->getElementType(), M, rewrite); remapped = PointerType::get(pointee, pointer->getAddressSpace()); break; } case Type::StructTyID: remapped = MapStructType(cast<StructType>(type), M, rewrite); break; case Type::ArrayTyID: { ArrayType *array = cast<ArrayType>(type); Type *element = MapType(array->getElementType(), M, rewrite); remapped = ArrayType::get(element, array->getNumElements()); break; } case Type::FunctionTyID: { FunctionType *function = cast<FunctionType>(type); SmallVector<Type *, 8> arg_types; for (auto *param : function->params()) { arg_types.push_back(MapType(param, M, rewrite)); } remapped = FunctionType::get(MapType(function->getReturnType(), M, rewrite), arg_types, function->isVarArg()); break; } default: break; } deferred_types_.erase(type); auto result = remapped_types_.insert(std::make_pair(type, remapped)); if (remapped != type && result.second && !remapped->isFunctionTy()) { // Record the type sizes from data layout to generate correct SPIRV-V // information later. const auto &DL = M.getDataLayout(); NamedMDNode *type_sizes_md = M.getOrInsertNamedMetadata(clspv::RemappedTypeSizesMetadataName()); auto *i64 = Type::getInt32Ty(M.getContext()); Metadata *size_values[3]; size_values[0] = ConstantAsMetadata::get( ConstantInt::get(i64, DL.getTypeSizeInBits(type))); size_values[1] = ConstantAsMetadata::get( ConstantInt::get(i64, DL.getTypeStoreSize(type))); size_values[2] = ConstantAsMetadata::get( ConstantInt::get(i64, DL.getTypeAllocSize(type))); MDTuple *values_md = MDTuple::get(M.getContext(), size_values); MDTuple *entry = MDTuple::get( M.getContext(), {ConstantAsMetadata::get(Constant::getNullValue(remapped)), values_md}); type_sizes_md->addOperand(entry); } return remapped; } StructType *UBOTypeTransformPass::MapStructType(StructType *struct_ty, Module &M, bool rewrite) { // We'll never have to remap an opaque struct. if (struct_ty->isOpaque()) return struct_ty; bool changed = false; SmallVector<Type *, 8> elements; SmallVector<uint64_t, 8> offsets; const auto *layout = M.getDataLayout().getStructLayout(struct_ty); for (unsigned i = 0; i != struct_ty->getNumElements(); ++i) { Type *element = struct_ty->getElementType(i); uint64_t offset = layout->getElementOffset(i); const auto *array = dyn_cast<ArrayType>(element); // Do not modify the element unless |rewrite| is true. if (rewrite && array && array->getElementType()->isIntegerTy(8) && !support_int8_array_) { // Unless char arrays in UBOs are supported, replace all instances with an // i32. // This is a padding element. If chars are supported, replace the array // with a single char, otherwise use an int replacement. if (clspv::Option::Int8Support()) { elements.push_back(Type::getInt8Ty(M.getContext())); } else { assert((array->getNumElements() % 4 == 0) && "Non-integer sized padding!"); elements.push_back(Type::getInt32Ty(M.getContext())); } } else { elements.push_back(MapType(element, M, rewrite)); } offsets.push_back(offset); changed |= (element != elements.back()); } if (changed) { StructType *replacement = StructType::create(elements, "", struct_ty->isPacked()); // Record the correct offsets for use when generating the SPIR-V binary. NamedMDNode *offsets_md = M.getOrInsertNamedMetadata(clspv::RemappedTypeOffsetMetadataName()); SmallVector<Metadata *, 8> offset_values; for (auto offset : offsets) { offset_values.push_back(ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(M.getContext()), offset))); } MDTuple *values_md = MDTuple::get(M.getContext(), offset_values); MDTuple *entry = MDTuple::get( M.getContext(), {ConstantAsMetadata::get(Constant::getNullValue(replacement)), values_md}); offsets_md->addOperand(entry); return replacement; } else { return struct_ty; } } bool UBOTypeTransformPass::RemapTypes(Module &M) { bool changed = false; // Functions with transformed types require rebuilding. SmallVector<Function *, 16> functions_to_modify; changed |= RemapFunctions(&functions_to_modify, M); // Global variables with transformed types require rebuilding. SmallVector<GlobalVariable *, 16> variables_to_modify; changed |= RemapGlobalVariables(&variables_to_modify, M); // Perform the type mutation within each function as necessary. for (auto &F : M) { for (auto &Arg : F.args()) { changed |= RemapValue(&Arg, M); } for (auto &BB : F) { for (auto &I : BB) { changed |= RemapUser(&I, M); if (auto *call = dyn_cast<CallInst>(&I)) { // Update the called function if we rewrote it. auto iter = function_replacements_.find(call->getCalledFunction()); if (iter != function_replacements_.end()) { call->setCalledFunction(iter->second->getFunctionType(), iter->second); } } else if (auto *gep = dyn_cast<GetElementPtrInst>(&I)) { // Fix the extra type in the GEP Type *source_ty = gep->getSourceElementType(); Type *remapped = RebuildType(source_ty, M); if (remapped != source_ty) { gep->setSourceElementType(remapped); } } } } } FixupFunctions(functions_to_modify, M); FixupGlobalVariables(variables_to_modify); return changed; } bool UBOTypeTransformPass::RemapFunctions( SmallVectorImpl<Function *> *functions_to_modify, Module &M) { bool changed = false; for (auto &F : M) { auto *remapped = RebuildType(F.getFunctionType(), M); if (F.getType() != remapped) { changed = true; functions_to_modify->push_back(&F); } } for (auto func : *functions_to_modify) { // Remove the function from the module, but keep it around for the time // being. func->removeFromParent(); auto *replacement_type = cast<FunctionType>(RebuildType(func->getFunctionType(), M)); // Insert the replacement function. Copy the calling convention, attributes // and metadata of the source function. auto inserted = M.getOrInsertFunction(func->getName(), replacement_type, func->getAttributes()); Function *replacement = cast<Function>(inserted.getCallee()); function_replacements_[func] = replacement; replacement->setCallingConv(func->getCallingConv()); replacement->copyMetadata(func, 0); // Move the basic blocks into the replacement function. if (!func->isDeclaration()) { std::vector<BasicBlock *> blocks; for (auto &BB : *func) { blocks.push_back(&BB); } for (auto *BB : blocks) { BB->removeFromParent(); BB->insertInto(replacement); } } } return changed; } bool UBOTypeTransformPass::RemapGlobalVariables( SmallVectorImpl<GlobalVariable *> *variables_to_modify, Module &M) { bool changed = false; for (auto &GV : M.globals()) { if (auto *ptr_ty = dyn_cast<PointerType>(GV.getType())) { auto *remapped = RebuildType(ptr_ty->getElementType(), M); if (ptr_ty->getElementType() != remapped) { changed = true; variables_to_modify->push_back(&GV); } } } for (auto *GV : *variables_to_modify) { GV->removeFromParent(); auto *replacement_type = RebuildType(GV->getType()->getPointerElementType(), M); Constant *initializer = nullptr; if (auto old_init = GV->getInitializer()) { initializer = RebuildConstant(old_init, replacement_type, M); } // Recreate the global variable. GlobalVariable *replacement = new GlobalVariable( M, replacement_type, GV->isConstant(), GV->getLinkage(), initializer, GV->getName(), /*InsertBefore=*/nullptr, GV->getThreadLocalMode(), GV->getType()->getPointerAddressSpace()); remapped_globals_[GV] = replacement; replacement->copyMetadata(GV, 0); } return changed; } bool UBOTypeTransformPass::RemapUser(User *user, Module &M) { if (isa<ConstantData>(user) || isa<ConstantAggregate>(user)) { return RemapConstant(cast<Constant>(user), M); } bool changed = RemapValue(user, M); for (Use &use : user->operands()) { User *operand_user = use.getUser(); if (!operand_user) { changed |= RemapValue(use.get(), M); } else if (!isa<Instruction>(operand_user) && !isa<GlobalValue>(operand_user) && !isa<Argument>(operand_user)) { // Keep mutating to handle constant expressions. changed |= RemapUser(operand_user, M); // If this was a constant that got rebuilt, update the operand. if (auto *constant = dyn_cast<Constant>(operand_user)) { auto iter = remapped_globals_.find(constant); if (iter != remapped_globals_.end()) { use.set(iter->second); changed = true; } } } } return changed; } bool UBOTypeTransformPass::RemapValue(Value *value, Module &M) { Type *remapped = RebuildType(value->getType(), M); if (remapped == value->getType()) return false; value->mutateType(remapped); return true; } bool UBOTypeTransformPass::RemapConstant(Constant *constant, Module &M) { // Rebuild the constant. Type *remapped = RebuildType(constant->getType(), M); if (remapped == constant->getType()) return false; RebuildConstant(constant, remapped, M); return true; } Constant *UBOTypeTransformPass::RebuildConstant(Constant *constant, Type *remapped_ty, Module &M) { if (constant->getType() == remapped_ty) return constant; // Check whether this constant has been rebuilt already. auto iter = remapped_globals_.find(constant); if (iter != remapped_globals_.end()) return iter->second; if (constant->isZeroValue()) { Constant *null_constant = Constant::getNullValue(remapped_ty); remapped_globals_[constant] = null_constant; return null_constant; } else if (isa<UndefValue>(constant)) { // This case should catch the padding transformations since the padding // can't be initialized. Constant *undef_constant = UndefValue::get(remapped_ty); remapped_globals_[constant] = undef_constant; return undef_constant; } else if (auto *agg_constant = dyn_cast<ConstantAggregate>(constant)) { auto *struct_ty = dyn_cast<StructType>(constant->getType()); auto *arr_ty = dyn_cast<ArrayType>(constant->getType()); auto *vec_ty = dyn_cast<VectorType>(constant->getType()); // CompositeType doesn't implement getNumElements(). unsigned num_elements = 0; if (struct_ty) num_elements = struct_ty->getNumElements(); else if (arr_ty) num_elements = arr_ty->getNumElements(); else if (vec_ty) num_elements = vec_ty->getElementCount().getKnownMinValue(); SmallVector<Constant *, 8> rebuilt_constants; for (unsigned i = 0; i != num_elements; ++i) { Constant *element_constant = agg_constant->getAggregateElement(i); Type *remapped_ele_ty = nullptr; if (struct_ty) { remapped_ele_ty = cast<StructType>(remapped_ty)->getTypeAtIndex(i); } else if (arr_ty) { remapped_ele_ty = remapped_ty->getArrayElementType(); } else if (vec_ty) { remapped_ele_ty = cast<VectorType>(remapped_ty)->getElementType(); } if (remapped_ele_ty != element_constant->getType()) { rebuilt_constants.push_back( RebuildConstant(element_constant, remapped_ele_ty, M)); } else { rebuilt_constants.push_back(element_constant); } } Constant *rebuilt = nullptr; if (auto remapped_struct_ty = dyn_cast<StructType>(remapped_ty)) { rebuilt = ConstantStruct::get(remapped_struct_ty, rebuilt_constants); } else if (auto remapped_array_ty = dyn_cast<ArrayType>(remapped_ty)) { rebuilt = ConstantArray::get(remapped_array_ty, rebuilt_constants); } else { rebuilt = ConstantVector::get(rebuilt_constants); } return rebuilt; } else { llvm_unreachable("rewriting scalar constant?"); } return constant; } void UBOTypeTransformPass::FixupFunctions( const ArrayRef<Function *> &functions_to_modify, Module &M) { // If functions were replaced, we have some final fixup to do: // * Rename arguments to maintain descriptor mapping // * Replace argument and function uses with their replacements. // // Note: type mutations occur to satisfy RAUW requirements. for (auto *func : functions_to_modify) { Function *replacement = function_replacements_[func]; for (auto arg_iter = func->arg_begin(), replace_iter = replacement->arg_begin(); arg_iter != func->arg_end(); ++arg_iter, ++replace_iter) { replace_iter->takeName(&*arg_iter); arg_iter->mutateType(replace_iter->getType()); arg_iter->replaceAllUsesWith(replace_iter); } func->mutateType(replacement->getType()); func->replaceAllUsesWith(replacement); delete func; } } void UBOTypeTransformPass::FixupGlobalVariables( const ArrayRef<GlobalVariable *> &variables_to_modify) { for (auto *var : variables_to_modify) { // Mutate type to satisfy RAUW requirements. auto *remapped_var = remapped_globals_[var]; var->mutateType(remapped_var->getType()); var->replaceAllUsesWith(remapped_var); delete var; } } } // namespace