blob: af42c2445f3b27ca0307fae4b0b7c6f30aa2be05 [file] [edit]
// 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 <vector>
#include "llvm/ADT/UniqueVector.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/Pass.h"
#include "UndoInstCombinePass.h"
#define DEBUG_TYPE "undoinstcombine"
using namespace llvm;
// This pass is run afer LongVectorLowering so the maximum vector size is 4
constexpr unsigned int max_vector_size = 4;
PreservedAnalyses clspv::UndoInstCombinePass::run(Module &M,
ModuleAnalysisManager &) {
PreservedAnalyses PA;
for (auto &F : M) {
runOnFunction(F);
}
// Cleanup.
for (auto inst : dead_)
inst->eraseFromParent();
for (auto val : potentially_dead_) {
if (auto inst = dyn_cast<Instruction>(val)) {
if (inst->user_empty())
inst->eraseFromParent();
} else if (auto cast = dyn_cast<BitCastOperator>(val)) {
if (auto constant = dyn_cast<Constant>(cast->getOperand(0)))
constant->removeDeadConstantUsers();
}
}
return PA;
}
bool clspv::UndoInstCombinePass::runOnFunction(Function &F) {
bool changed = false;
for (auto &BB : F) {
for (auto &I : BB) {
changed |= UndoWideVectorExtractCast(&I);
changed |= UndoWideVectorShuffleCast(&I);
changed |= UndoNarrowedIntDiv(&I);
}
}
return changed;
}
VectorType *InferTypeForOpaqueLoad(VectorType *old_type) {
auto vec_size = old_type->getElementCount().getKnownMinValue();
auto bit_width = cast<IntegerType>(old_type->getElementType())->getBitWidth();
// All the integers in OpenCL C have a power of two bit width
auto is_pow_of_two = [](auto x) { return (x & (x - 1)) == 0; };
// If vec size 4 or 3 doesn't fit the index then smaller sizes won't allow
// greater granularity
uint64_t new_size =
vec_size % 4 == 0 && is_pow_of_two((vec_size / 4) * bit_width) ? 4 : 3;
uint64_t divisor = vec_size / new_size;
auto new_bit_width = bit_width * divisor;
return VectorType::get(
IntegerType::get(old_type->getContext(), new_bit_width), new_size,
old_type->getElementCount().isScalable());
}
bool clspv::UndoInstCombinePass::UndoWideVectorExtractCast(Instruction *inst) {
auto extract = dyn_cast<ExtractElementInst>(inst);
if (!extract)
return false;
auto vec_ty = extract->getVectorOperandType();
auto vec_size = vec_ty->getElementCount().getKnownMinValue();
if (vec_size <= max_vector_size)
return false;
// Instcombine only transforms TruncInst (which operates on integers).
if (!vec_ty->getElementType()->isIntegerTy())
return false;
auto const_idx = dyn_cast<ConstantInt>(extract->getIndexOperand());
if (!const_idx)
return false;
auto extract_src = extract->getVectorOperand();
auto load = dyn_cast<LoadInst>(extract_src);
// If this is a load, check for a cast on the pointer operand
auto cast =
dyn_cast<BitCastOperator>(load ? load->getPointerOperand() : extract_src);
Value *src =
cast ? cast->getOperand(0) : (load ? load->getPointerOperand() : nullptr);
if (!src)
return false;
auto src_ty = src->getType();
VectorType *src_vec_ty = [src_ty, vec_ty] {
if (src_ty->isPointerTy()) {
return InferTypeForOpaqueLoad(vec_ty);
} else {
return dyn_cast<VectorType>(src_ty);
}
}();
if (!src_vec_ty)
return false;
uint64_t src_elements = src_vec_ty->getElementCount().getKnownMinValue();
if (vec_size <= src_elements)
return false;
uint64_t idx = const_idx->getZExtValue();
uint64_t ratio = vec_size / src_elements;
uint64_t new_idx = idx / ratio;
// Instcombine should never have generated an odd index, so don't handle
// right now.
if (idx & 0x1)
return false;
IRBuilder<> builder(inst);
src = load ? builder.CreateLoad(src_vec_ty, src) : src;
if (load) {
potentially_dead_.insert(load);
}
auto new_src = builder.CreateExtractElement(src, builder.getInt32(new_idx));
auto trunc = builder.CreateTrunc(new_src, extract->getType());
extract->replaceAllUsesWith(trunc);
dead_.push_back(extract);
if (cast)
potentially_dead_.insert(cast);
return true;
}
bool clspv::UndoInstCombinePass::UndoWideVectorShuffleCast(Instruction *inst) {
auto shuffle = dyn_cast<ShuffleVectorInst>(inst);
if (!shuffle)
return false;
// Instcombine only produces shuffles with an undef second input, so don't
// handle other cases for now.
if (!isa<UndefValue>(shuffle->getOperand(1)))
return false;
// Instcombine only transforms TruncInst (which operates on integers).
auto vec_ty = cast<VectorType>(shuffle->getType());
if (!vec_ty->getElementType()->isIntegerTy())
return false;
auto in1 = shuffle->getOperand(0);
auto in1_vec_ty = cast<VectorType>(in1->getType());
auto in1_vec_size = in1_vec_ty->getElementCount().getKnownMinValue();
if (in1_vec_size <= max_vector_size)
return false;
auto in1_load = dyn_cast<LoadInst>(in1);
// If this is a load, check for a cast on the pointer operand
auto in1_cast =
dyn_cast<BitCastOperator>(in1_load ? in1_load->getPointerOperand() : in1);
Value *src = in1_cast ? in1_cast->getOperand(0)
: (in1_load ? in1_load->getPointerOperand() : nullptr);
if (!src)
return false;
auto src_ty = src->getType();
VectorType *src_vec_ty = [src_ty, in1_vec_ty] {
if (src_ty->isPointerTy()) {
return InferTypeForOpaqueLoad(in1_vec_ty);
} else {
return dyn_cast<VectorType>(src_ty);
}
}();
if (!src_vec_ty)
return false;
uint64_t src_elements = src_vec_ty->getElementCount().getKnownMinValue();
if (in1_vec_size <= src_elements)
return false;
uint64_t ratio = in1_vec_size / src_elements;
auto dst_scalar_type = vec_ty->getElementType();
SmallVector<int, 16> mask;
shuffle->getShuffleMask(mask);
for (auto i : mask) {
// Instcombine should not have generated odd indices, so don't handle them
// for now.
if ((i != PoisonMaskElem) && (i & 0x1))
return false;
}
// For each index, create a truncate of an extract element and insert each
// into the result vector.
IRBuilder<> builder(inst);
Value *insert = nullptr;
if (in1_load) {
potentially_dead_.insert(in1_load);
src = builder.CreateLoad(src_vec_ty, src);
}
// TODO could replace with a shuffle and vectorized trunc
int i = 0;
for (auto idx : mask) {
if (idx == PoisonMaskElem)
continue;
uint64_t new_idx = idx / ratio;
auto extract = builder.CreateExtractElement(src, builder.getInt32(new_idx));
auto trunc = builder.CreateTrunc(extract, dst_scalar_type);
Value *prev = insert ? insert : Constant::getNullValue(vec_ty);
insert = builder.CreateInsertElement(prev, trunc, builder.getInt32(i++));
}
if (!insert) {
insert = Constant::getNullValue(vec_ty);
}
shuffle->replaceAllUsesWith(insert);
dead_.push_back(shuffle);
if (in1_cast)
potentially_dead_.insert(in1_cast);
return true;
}
bool clspv::UndoInstCombinePass::UndoNarrowedIntDiv(Instruction *inst) {
auto cast = dyn_cast<CastInst>(inst);
if (!cast)
return false;
auto opcode = cast->getOpcode();
if (opcode != Instruction::SExt && opcode != Instruction::ZExt)
return false;
auto src = cast->getOperand(0);
auto binop = dyn_cast<BinaryOperator>(src);
if (!binop)
return false;
auto binop_code = binop->getOpcode();
bool is_signed = (opcode == Instruction::SExt);
if (is_signed) {
if (binop_code != Instruction::SDiv && binop_code != Instruction::SRem)
return false;
} else {
if (binop_code != Instruction::UDiv && binop_code != Instruction::URem)
return false;
}
auto dst_ty = cast->getType();
auto src_ty = binop->getType();
if (dst_ty == src_ty)
return false;
auto dst_elem_ty = dst_ty->getScalarType();
auto src_elem_ty = src_ty->getScalarType();
if (!dst_elem_ty->isIntegerTy() || !src_elem_ty->isIntegerTy())
return false;
if (dst_elem_ty->getIntegerBitWidth() <= src_elem_ty->getIntegerBitWidth())
return false;
IRBuilder<> builder(cast);
Value *lhs = binop->getOperand(0);
Value *rhs = binop->getOperand(1);
auto get_widened_operand = [&](Value *val) -> Value * {
if (auto *trunc = dyn_cast<TruncInst>(val)) {
if (trunc->getOperand(0)->getType() == dst_ty) {
if ((is_signed && trunc->hasNoSignedWrap()) ||
(!is_signed && trunc->hasNoUnsignedWrap())) {
potentially_dead_.insert(trunc);
return trunc->getOperand(0);
}
}
}
return is_signed ? builder.CreateSExt(val, dst_ty)
: builder.CreateZExt(val, dst_ty);
};
Value *new_lhs = get_widened_operand(lhs);
Value *new_rhs = get_widened_operand(rhs);
Value *new_binop = builder.CreateBinOp(binop_code, new_lhs, new_rhs);
cast->replaceAllUsesWith(new_binop);
dead_.push_back(cast);
potentially_dead_.insert(binop);
return true;
}