| // Copyright 2020 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. |
| |
| #include "PushConstant.h" |
| |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/Metadata.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/IR/Type.h" |
| #include "llvm/Support/ErrorHandling.h" |
| |
| #include "clspv/Option.h" |
| |
| #include "BitcastUtils.h" |
| #include "Constants.h" |
| |
| using namespace llvm; |
| |
| namespace clspv { |
| |
| const char *GetPushConstantName(PushConstant pc) { |
| switch (pc) { |
| case PushConstant::Dimensions: |
| return "dimensions"; |
| case PushConstant::GlobalOffset: |
| return "global_offset"; |
| case PushConstant::EnqueuedLocalSize: |
| return "enqueued_local_size"; |
| case PushConstant::GlobalSize: |
| return "global_size"; |
| case PushConstant::RegionOffset: |
| return "region_offset"; |
| case PushConstant::NumWorkgroups: |
| return "num_workgroups"; |
| case PushConstant::RegionGroupOffset: |
| return "region_group_offset"; |
| case PushConstant::KernelArgument: |
| return "kernel_argument"; |
| case PushConstant::ImageMetadata: |
| return "image_metadata"; |
| case PushConstant::ModuleConstantsPointer: |
| return "module_constants_pointer"; |
| case PushConstant::PrintfBufferPointer: |
| return "printf_buffer_pointer"; |
| case PushConstant::NormalizedSamplerMask: |
| return "normalized_sampler_mask"; |
| } |
| llvm_unreachable("Unknown PushConstant in GetPushConstantName"); |
| return ""; |
| } |
| |
| Type *GetPushConstantType(Module &M, PushConstant pc) { |
| auto &C = M.getContext(); |
| switch (pc) { |
| case PushConstant::Dimensions: |
| return IntegerType::get(C, 32); |
| case PushConstant::GlobalOffset: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::EnqueuedLocalSize: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::GlobalSize: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::RegionOffset: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::NumWorkgroups: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::RegionGroupOffset: |
| return FixedVectorType::get(IntegerType::get(C, 32), 3); |
| case PushConstant::ImageMetadata: |
| return IntegerType::get(C, 32); |
| case PushConstant::ModuleConstantsPointer: |
| return IntegerType::get(C, 64); |
| case PushConstant::PrintfBufferPointer: |
| return IntegerType::get(C, 64); |
| case PushConstant::NormalizedSamplerMask: |
| return IntegerType::get(C, 64); |
| default: |
| break; |
| } |
| llvm_unreachable("Unknown PushConstant in GetPushConstantType"); |
| return nullptr; |
| } |
| |
| Value *GetPushConstantPointer(BasicBlock *BB, PushConstant pc, |
| const ArrayRef<Value *> &extra_indices) { |
| auto M = BB->getParent()->getParent(); |
| |
| // Get variable |
| auto GV = M->getGlobalVariable(clspv::PushConstantsVariableName()); |
| assert(GV && "Push constants requested but none are declared."); |
| |
| // Find requested pc in metadata |
| auto MD = GV->getMetadata(clspv::PushConstantsMetadataName()); |
| #ifndef NDEBUG |
| bool found = false; |
| #endif |
| uint32_t idx = 0; |
| for (auto &PCMD : MD->operands()) { |
| auto mpc = static_cast<PushConstant>( |
| mdconst::extract<ConstantInt>(PCMD)->getZExtValue()); |
| if (mpc == pc) { |
| #ifndef NDEBUG |
| found = true; |
| #endif |
| break; |
| } |
| idx++; |
| } |
| |
| // Assert that it exists |
| assert(found && "Push constant wasn't declared."); |
| |
| // Construct pointer |
| IRBuilder<> Builder(BB); |
| SmallVector<Value *, 4> Indices(2); |
| Indices[0] = Builder.getInt32(0); |
| Indices[1] = Builder.getInt32(idx); |
| for (auto idx : extra_indices) |
| Indices.push_back(idx); |
| return Builder.CreateInBoundsGEP(GV->getValueType(), GV, Indices); |
| } |
| |
| bool UsesGlobalPushConstants(Module &M) { |
| return ShouldDeclareGlobalOffsetPushConstant(M) || |
| ShouldDeclareEnqueuedLocalSizePushConstant(M) || |
| ShouldDeclareGlobalSizePushConstant(M) || |
| ShouldDeclareRegionOffsetPushConstant(M) || |
| ShouldDeclareNumWorkgroupsPushConstant(M) || |
| ShouldDeclareRegionGroupOffsetPushConstant(M) || |
| ShouldDeclareModuleConstantsPointerPushConstant(M) || |
| ShouldDeclarePrintfBufferPointerPushConstant(M); |
| } |
| |
| bool ShouldDeclareGlobalOffsetPushConstant(Module &M) { |
| bool isEnabled = (clspv::Option::GlobalOffset() && |
| clspv::Option::NonUniformNDRangeSupported()) || |
| clspv::Option::GlobalOffsetPushConstant(); |
| bool isUsed = (M.getFunction("_Z17get_global_offsetj") != nullptr) || |
| (M.getFunction("_Z13get_global_idj") != nullptr) || |
| (M.getFunction("_Z20get_global_linear_idv") != nullptr); |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareEnqueuedLocalSizePushConstant(Module &M) { |
| bool isEnabled = clspv::Option::NonUniformNDRangeSupported(); |
| bool isUsed = (M.getFunction("_Z23get_enqueued_local_sizej") != nullptr) || |
| (M.getFunction("_Z27get_enqueued_num_sub_groupsv") != nullptr); |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareGlobalSizePushConstant(Module &M) { |
| bool isEnabled = clspv::Option::NonUniformNDRangeSupported(); |
| bool isUsed = M.getFunction("_Z15get_global_sizej") != nullptr || |
| M.getFunction("_Z20get_global_linear_idv") != nullptr; |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareRegionOffsetPushConstant(Module &M) { |
| bool isEnabled = clspv::Option::NonUniformNDRangeSupported(); |
| bool isUsed = M.getFunction("_Z13get_global_idj") != nullptr || |
| M.getFunction("_Z20get_global_linear_idv") != nullptr; |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareNumWorkgroupsPushConstant(Module &M) { |
| bool isEnabled = clspv::Option::NonUniformNDRangeSupported(); |
| bool isUsed = M.getFunction("_Z14get_num_groupsj") != nullptr; |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareRegionGroupOffsetPushConstant(Module &M) { |
| bool isEnabled = clspv::Option::NonUniformNDRangeSupported(); |
| bool isUsed = M.getFunction("_Z12get_group_idj") != nullptr; |
| return isEnabled && isUsed; |
| } |
| |
| bool ShouldDeclareModuleConstantsPointerPushConstant(Module &M) { |
| bool isEnabled = clspv::Option::ModuleConstantsInStorageBuffer() && |
| clspv::Option::PhysicalStorageBuffers(); |
| |
| bool moduleHasGlobals = false; |
| for (GlobalVariable &GV : M.globals()) { |
| if (GV.hasInitializer() && GV.getType()->getPointerAddressSpace() == |
| clspv::AddressSpace::Constant) { |
| if (!GV.use_empty()) { |
| moduleHasGlobals = true; |
| break; |
| } |
| } |
| } |
| |
| return isEnabled && moduleHasGlobals; |
| } |
| |
| bool ShouldDeclarePrintfBufferPointerPushConstant(Module &M) { |
| bool isEnabled = |
| clspv::Option::PrintfSupport() && clspv::Option::PhysicalStorageBuffers(); |
| bool isUsed = M.getFunction("printf") != nullptr; |
| return isEnabled && isUsed; |
| } |
| |
| StructType *GlobalPushConstantsType(Module &M) { |
| if (auto GV = M.getGlobalVariable(clspv::PushConstantsVariableName())) { |
| if (auto *STy = dyn_cast<StructType>(GV->getValueType())) { |
| return STy; |
| } |
| } |
| SmallVector<Type *, 8> types; |
| if (ShouldDeclareGlobalOffsetPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::GlobalOffset); |
| types.push_back(type); |
| } |
| if (ShouldDeclareEnqueuedLocalSizePushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::EnqueuedLocalSize); |
| types.push_back(type); |
| } |
| if (ShouldDeclareGlobalSizePushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::GlobalSize); |
| types.push_back(type); |
| } |
| if (ShouldDeclareRegionOffsetPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::RegionOffset); |
| types.push_back(type); |
| } |
| if (ShouldDeclareNumWorkgroupsPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::NumWorkgroups); |
| types.push_back(type); |
| } |
| if (ShouldDeclareRegionGroupOffsetPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::RegionGroupOffset); |
| types.push_back(type); |
| } |
| if (ShouldDeclareModuleConstantsPointerPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::ModuleConstantsPointer); |
| types.push_back(type); |
| } |
| if (ShouldDeclarePrintfBufferPointerPushConstant(M)) { |
| auto type = GetPushConstantType(M, PushConstant::PrintfBufferPointer); |
| types.push_back(type); |
| } |
| |
| return StructType::get(M.getContext(), types, false); |
| } |
| |
| const uint64_t kIntBytes = 4; |
| |
| void RedeclareGlobalPushConstants(Module &M, StructType *mangled_struct_ty, |
| int push_constant_type) { |
| auto old_GV = M.getGlobalVariable(clspv::PushConstantsVariableName()); |
| |
| std::vector<Type *> push_constant_tys; |
| if (old_GV) { |
| auto block_ty = cast<StructType>(old_GV->getValueType()); |
| for (auto ele : block_ty->elements()) |
| push_constant_tys.push_back(ele); |
| } |
| push_constant_tys.push_back(mangled_struct_ty); |
| |
| auto push_constant_ty = StructType::create(M.getContext(), push_constant_tys); |
| auto new_GV = new GlobalVariable( |
| M, push_constant_ty, false, GlobalValue::ExternalLinkage, nullptr, "", |
| nullptr, GlobalValue::ThreadLocalMode::NotThreadLocal, |
| clspv::AddressSpace::PushConstant); |
| new_GV->setInitializer(Constant::getNullValue(push_constant_ty)); |
| std::vector<Metadata *> md_args; |
| if (old_GV) { |
| // Replace the old push constant variable metadata and uses. |
| new_GV->takeName(old_GV); |
| auto md = old_GV->getMetadata(clspv::PushConstantsMetadataName()); |
| for (auto &op : md->operands()) { |
| md_args.push_back(op.get()); |
| } |
| std::vector<User *> users; |
| for (auto user : old_GV->users()) |
| users.push_back(user); |
| for (auto user : users) { |
| if (auto gep = dyn_cast<GetElementPtrInst>(user)) { |
| // Most uses are likely constant geps, but handle instructions first |
| // since we can only really access gep operators for the constant side. |
| SmallVector<Value *, 4> indices; |
| for (auto iter = gep->idx_begin(); iter != gep->idx_end(); ++iter) { |
| indices.push_back(*iter); |
| } |
| auto new_gep = GetElementPtrInst::Create( |
| push_constant_ty, new_GV, indices, "", gep->getIterator()); |
| new_gep->setIsInBounds(gep->isInBounds()); |
| gep->replaceAllUsesWith(new_gep); |
| gep->eraseFromParent(); |
| } else if (auto gep_operator = dyn_cast<GEPOperator>(user)) { |
| int steps; |
| bool perfect_match; |
| bool alias_found = BitcastUtils::FindAliasingContainedType( |
| push_constant_ty, gep_operator->getSourceElementType(), steps, |
| perfect_match, M.getDataLayout()); |
| |
| SmallVector<Constant *, 4> indices; |
| if (alias_found) { |
| for (int i = 0; i < steps; i++) { |
| indices.push_back( |
| ConstantInt::get(Type::getInt32Ty(M.getContext()), 0)); |
| } |
| } |
| |
| for (auto iter = gep_operator->idx_begin(); |
| iter != gep_operator->idx_end(); ++iter) { |
| auto cst = cast<Constant>(*iter); |
| if (cst->getType()->isIntegerTy(64) && |
| GetElementPtrInst::getIndexedType(push_constant_ty, indices) |
| ->isStructTy()) { |
| cst = ConstantInt::get(IntegerType::get(M.getContext(), 32), |
| cast<ConstantInt>(cst)->getZExtValue()); |
| } |
| indices.push_back(cst); |
| } |
| auto new_gep = ConstantExpr::getGetElementPtr( |
| push_constant_ty, new_GV, indices, gep_operator->isInBounds()); |
| user->replaceAllUsesWith(new_gep); |
| } else if (auto load = dyn_cast<LoadInst>(user)) { |
| auto new_load = |
| new LoadInst(load->getType(), new_GV, "", load->getIterator()); |
| load->replaceAllUsesWith(new_load); |
| load->eraseFromParent(); |
| } else { |
| user->print(errs()); |
| assert(false && "unexpected global use"); |
| } |
| } |
| old_GV->removeDeadConstantUsers(); |
| old_GV->eraseFromParent(); |
| } else { |
| new_GV->setName(clspv::PushConstantsVariableName()); |
| } |
| // New metadata operand for the kernel arguments. |
| auto cst = ConstantInt::get(IntegerType::get(M.getContext(), 32), |
| static_cast<int>(push_constant_type)); |
| md_args.push_back(ConstantAsMetadata::get(cst)); |
| new_GV->setMetadata(clspv::PushConstantsMetadataName(), |
| MDNode::get(M.getContext(), md_args)); |
| } |
| |
| Value *ConvertToType(Module &M, StructType *pod_struct, unsigned index, |
| IRBuilder<> &builder) { |
| auto int32_ty = IntegerType::get(M.getContext(), 32); |
| const auto &DL = M.getDataLayout(); |
| const auto struct_layout = DL.getStructLayout(pod_struct); |
| auto ele_ty = pod_struct->getElementType(index); |
| const auto ele_size = DL.getTypeStoreSize(ele_ty).getKnownMinValue(); |
| auto ele_offset = struct_layout->getElementOffset(index); |
| const auto ele_start_index = ele_offset / kIntBytes; // round down |
| const auto ele_end_index = |
| (ele_offset + ele_size + kIntBytes - 1) / kIntBytes; // round up |
| |
| // Load the right number of ints. We'll load at least one, but may load |
| // ele_size / 4 + 1 integers depending on the offset. |
| std::vector<Value *> int_elements; |
| uint32_t i = ele_start_index; |
| do { |
| auto gep = clspv::GetPushConstantPointer( |
| builder.GetInsertBlock(), clspv::PushConstant::KernelArgument, |
| {builder.getInt32(i)}); |
| auto ld = builder.CreateLoad(int32_ty, gep); |
| int_elements.push_back(ld); |
| i++; |
| } while (i < ele_end_index); |
| |
| return BuildFromElements(M, builder, ele_ty, ele_offset % kIntBytes, 0, |
| int_elements); |
| } |
| |
| Value *BuildFromElements(Module &M, IRBuilder<> &builder, Type *dst_type, |
| uint64_t base_offset, uint64_t base_index, |
| const std::vector<Value *> &elements) { |
| auto int32_ty = IntegerType::get(M.getContext(), 32); |
| const auto &DL = M.getDataLayout(); |
| const auto dst_size = DL.getTypeStoreSize(dst_type).getKnownMinValue(); |
| auto dst_array_ty = dyn_cast<ArrayType>(dst_type); |
| auto dst_vec_ty = dyn_cast<VectorType>(dst_type); |
| |
| Value *dst = nullptr; |
| if (auto dst_struct_ty = dyn_cast<StructType>(dst_type)) { |
| // Create an insertvalue chain for each converted element. |
| auto struct_layout = DL.getStructLayout(dst_struct_ty); |
| for (uint32_t i = 0; i < dst_struct_ty->getNumElements(); ++i) { |
| auto ele_ty = dst_struct_ty->getTypeAtIndex(i); |
| const auto ele_offset = struct_layout->getElementOffset(i); |
| const auto index = base_index + (ele_offset / kIntBytes); |
| const auto offset = (base_offset + ele_offset) % kIntBytes; |
| |
| auto tmp = BuildFromElements(M, builder, ele_ty, offset, index, elements); |
| dst = builder.CreateInsertValue(dst ? dst : PoisonValue::get(dst_type), |
| tmp, {i}); |
| } |
| } else if (dst_array_ty || dst_vec_ty) { |
| if (dst_vec_ty && dst_vec_ty->getPrimitiveSizeInBits() == |
| int32_ty->getPrimitiveSizeInBits()) { |
| // Easy case is just a bitcast. |
| dst = builder.CreateBitCast(elements[base_index], dst_type); |
| } else if (dst_vec_ty && |
| dst_vec_ty->getElementType()->getPrimitiveSizeInBits() < |
| int32_ty->getPrimitiveSizeInBits()) { |
| // Bitcast integers to a vector of the primitive type and then shuffle |
| // elements into the final vector. |
| // |
| // We need at most two integers to handle any case here. |
| auto ele_ty = dst_vec_ty->getElementType(); |
| uint32_t num_elements = dst_vec_ty->getElementCount().getKnownMinValue(); |
| uint32_t ratio = (int32_ty->getPrimitiveSizeInBits() / |
| ele_ty->getPrimitiveSizeInBits()); |
| auto scaled_vec_ty = FixedVectorType::get(ele_ty, ratio); |
| SmallVector<Value *, 4> casts; |
| uint32_t num_ints = (num_elements + ratio - 1) / ratio; // round up |
| num_ints = std::max(num_ints, 1u); |
| for (uint32_t i = 0; i < num_ints; ++i) { |
| casts.push_back( |
| builder.CreateBitCast(elements[base_index + i], scaled_vec_ty)); |
| } |
| SmallVector<int, 4> indices(num_elements); |
| uint32_t i = base_offset * ratio / kIntBytes; |
| std::generate_n(indices.data(), num_elements, [&i]() { return i++; }); |
| switch (casts.size()) { |
| case 1: |
| // The only types that results in 1 cast are char2 and char3. |
| dst = builder.CreateShuffleVector(casts[0], indices); |
| break; |
| case 2: |
| dst = builder.CreateShuffleVector(casts[0], casts[1], indices); |
| break; |
| case 4: { |
| SmallVector<int, 4> inter_indices({0, 1, 2, 3}); |
| // Four casts could be 8 wide, 16-bit ele type or 16 wide 8-bit ele |
| // type. We can determine this by looking at the known min width of |
| // our cast vectors and setting up shuffle indices according. |
| auto vec_size = cast<VectorType>(casts[0]->getType()) |
| ->getElementCount() |
| .getKnownMinValue(); |
| if (vec_size == 4) { |
| inter_indices.append({4, 5, 6, 7}); |
| } else if(vec_size != 2) { |
| llvm_unreachable("Impossible cast vector size encountered."); |
| } |
| auto lo = |
| builder.CreateShuffleVector(casts[0], casts[1], inter_indices); |
| auto hi = |
| builder.CreateShuffleVector(casts[2], casts[3], inter_indices); |
| dst = builder.CreateShuffleVector(lo, hi, indices); |
| break; |
| } |
| case 8: { |
| // The only case with 8 casts is a vector of 16 width, 16-bit element |
| // type. We can reconstruct this by first shuffling them into 2 vec8s |
| // and then into the final vec16. |
| SmallVector<int, 8> inter_indices = {0, 1, 2, 3}; |
| auto lo = builder.CreateShuffleVector(casts[0], casts[1], inter_indices); |
| auto hi = builder.CreateShuffleVector(casts[2], casts[3], inter_indices); |
| auto lo2 = builder.CreateShuffleVector(casts[4], casts[5], inter_indices); |
| auto hi2 = builder.CreateShuffleVector(casts[6], casts[7], inter_indices); |
| inter_indices.append({4, 5, 6, 7}); |
| auto lo3 = builder.CreateShuffleVector(lo, hi, inter_indices); |
| auto hi3 = builder.CreateShuffleVector(lo2, hi2, inter_indices); |
| dst = builder.CreateShuffleVector(lo3, hi3, indices); |
| } |
| } |
| } else { |
| // General case, break into elements and construct the composite type. |
| auto ele_ty = dst_vec_ty ? dst_vec_ty->getElementType() |
| : dst_array_ty->getElementType(); |
| assert((DL.getTypeStoreSize(ele_ty).getKnownMinValue() < kIntBytes || |
| base_offset == 0) && |
| "Unexpected packed data format"); |
| uint64_t ele_size = DL.getTypeStoreSize(ele_ty); |
| uint32_t num_elements = |
| dst_vec_ty ? dst_vec_ty->getElementCount().getKnownMinValue() |
| : dst_array_ty->getNumElements(); |
| |
| // Arrays of shorts/halfs could be offset from the start of an int. |
| uint64_t bytes_consumed = 0; |
| for (uint32_t i = 0; i < num_elements; ++i) { |
| uint64_t ele_offset = (base_offset + bytes_consumed) % kIntBytes; |
| uint64_t ele_index = |
| base_index + (base_offset + bytes_consumed) / kIntBytes; |
| // Convert the element. |
| auto tmp = BuildFromElements(M, builder, ele_ty, ele_offset, ele_index, |
| elements); |
| if (dst_vec_ty) { |
| dst = builder.CreateInsertElement( |
| dst ? dst : PoisonValue::get(dst_type), tmp, i); |
| } else { |
| dst = builder.CreateInsertValue( |
| dst ? dst : PoisonValue::get(dst_type), tmp, {i}); |
| } |
| |
| // Track consumed bytes. |
| bytes_consumed += ele_size; |
| } |
| } |
| } else { |
| // Base case is scalar conversion. |
| if (dst_size < kIntBytes) { |
| dst = elements[base_index]; |
| if (dst_type->isIntegerTy() && base_offset == 0) { |
| // Can generate a single truncate instruction in this case. |
| dst = builder.CreateTrunc( |
| dst, IntegerType::get(M.getContext(), dst_size * 8)); |
| } else { |
| // Bitcast to a vector of |dst_type| and extract the right element. This |
| // avoids introducing i16 when converting to half. |
| uint32_t ratio = (int32_ty->getPrimitiveSizeInBits() / |
| dst_type->getPrimitiveSizeInBits()); |
| auto vec_ty = FixedVectorType::get(dst_type, ratio); |
| dst = builder.CreateBitCast(dst, vec_ty); |
| dst = builder.CreateExtractElement(dst, base_offset / dst_size); |
| } |
| } else if (dst_size == kIntBytes) { |
| assert(base_offset == 0 && "Unexpected packed data format"); |
| // Create a bit cast if necessary. |
| dst = elements[base_index]; |
| if (dst_type != int32_ty) dst = builder.CreateBitCast(dst, dst_type); |
| } else { |
| assert(base_offset == 0 && "Unexpected packed data format"); |
| assert(dst_size == kIntBytes * 2 && "Expected 64-bit scalar"); |
| // Round up to number of integers. |
| auto dst_int = IntegerType::get(M.getContext(), dst_size * 8); |
| auto zext0 = builder.CreateZExt(elements[base_index], dst_int); |
| auto zext1 = builder.CreateZExt(elements[base_index + 1], dst_int); |
| auto shl = builder.CreateShl(zext1, 32); |
| dst = builder.CreateOr({zext0, shl}); |
| if (dst_type != dst->getType()) |
| dst = builder.CreateBitCast(dst, dst_type); |
| } |
| } |
| |
| return dst; |
| } |
| |
| } // namespace clspv |