[fir] Add !fir.alloca conversion

Convert !fir.alloca into !llvm.alloca.

This also contains a fix for verifyInType in FIROps.cpp, to make sure we
can handle a !fir.ptr<!fir.array<?xN>>.

Differential Revision: https://reviews.llvm.org/D113563

Co-authored-by: Eric Schweitz <eschweitz@nvidia.com>
Co-authored-by: Jean Perier <jperier@nvidia.com>
This commit is contained in:
Diana Picus 2021-11-08 10:59:48 +00:00
parent bb2018261e
commit 1e6d9c06a5
4 changed files with 260 additions and 12 deletions

View File

@ -32,6 +32,14 @@
static constexpr unsigned kAttrPointer = CFI_attribute_pointer;
static constexpr unsigned kAttrAllocatable = CFI_attribute_allocatable;
static mlir::LLVM::ConstantOp
genConstantIndex(mlir::Location loc, mlir::Type ity,
mlir::ConversionPatternRewriter &rewriter,
std::int64_t offset) {
auto cattr = rewriter.getI64IntegerAttr(offset);
return rewriter.create<mlir::LLVM::ConstantOp>(loc, ity, cattr);
}
namespace {
/// FIR conversion pattern template
template <typename FromOp>
@ -145,6 +153,27 @@ protected:
return rewriter.create<mlir::LLVM::GEPOp>(loc, ty, base, cv);
}
/// Perform an extension or truncation as needed on an integer value. Lowering
/// to the specific target may involve some sign-extending or truncation of
/// values, particularly to fit them from abstract box types to the
/// appropriate reified structures.
mlir::Value integerCast(mlir::Location loc,
mlir::ConversionPatternRewriter &rewriter,
mlir::Type ty, mlir::Value val) const {
auto valTy = val.getType();
// If the value was not yet lowered, lower its type so that it can
// be used in getPrimitiveTypeSizeInBits.
if (!valTy.isa<mlir::IntegerType>())
valTy = convertType(valTy);
auto toSize = mlir::LLVM::getPrimitiveTypeSizeInBits(ty);
auto fromSize = mlir::LLVM::getPrimitiveTypeSizeInBits(valTy);
if (toSize < fromSize)
return rewriter.create<mlir::LLVM::TruncOp>(loc, ty, val);
if (toSize > fromSize)
return rewriter.create<mlir::LLVM::SExtOp>(loc, ty, val);
return val;
}
fir::LLVMTypeConverter &lowerTy() const {
return *static_cast<fir::LLVMTypeConverter *>(this->getTypeConverter());
}
@ -182,6 +211,92 @@ struct AddrOfOpConversion : public FIROpConversion<fir::AddrOfOp> {
return success();
}
};
} // namespace
/// Lookup the function to compute the memory size of this parametric derived
/// type. The size of the object may depend on the LEN type parameters of the
/// derived type.
static mlir::LLVM::LLVMFuncOp
getDependentTypeMemSizeFn(fir::RecordType recTy, fir::AllocaOp op,
mlir::ConversionPatternRewriter &rewriter) {
auto module = op->getParentOfType<mlir::ModuleOp>();
std::string name = recTy.getName().str() + "P.mem.size";
return module.lookupSymbol<mlir::LLVM::LLVMFuncOp>(name);
}
namespace {
/// convert to LLVM IR dialect `alloca`
struct AllocaOpConversion : public FIROpConversion<fir::AllocaOp> {
using FIROpConversion::FIROpConversion;
mlir::LogicalResult
matchAndRewrite(fir::AllocaOp alloc, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const override {
mlir::ValueRange operands = adaptor.getOperands();
auto loc = alloc.getLoc();
mlir::Type ity = lowerTy().indexType();
unsigned i = 0;
mlir::Value size = genConstantIndex(loc, ity, rewriter, 1).getResult();
mlir::Type ty = convertType(alloc.getType());
mlir::Type resultTy = ty;
if (alloc.hasLenParams()) {
unsigned end = alloc.numLenParams();
llvm::SmallVector<mlir::Value> lenParams;
for (; i < end; ++i)
lenParams.push_back(operands[i]);
mlir::Type scalarType = fir::unwrapSequenceType(alloc.getInType());
if (auto chrTy = scalarType.dyn_cast<fir::CharacterType>()) {
fir::CharacterType rawCharTy = fir::CharacterType::getUnknownLen(
chrTy.getContext(), chrTy.getFKind());
ty = mlir::LLVM::LLVMPointerType::get(convertType(rawCharTy));
assert(end == 1);
size = integerCast(loc, rewriter, ity, lenParams[0]);
} else if (auto recTy = scalarType.dyn_cast<fir::RecordType>()) {
mlir::LLVM::LLVMFuncOp memSizeFn =
getDependentTypeMemSizeFn(recTy, alloc, rewriter);
if (!memSizeFn)
emitError(loc, "did not find allocation function");
mlir::NamedAttribute attr = rewriter.getNamedAttr(
"callee", mlir::SymbolRefAttr::get(memSizeFn));
auto call = rewriter.create<mlir::LLVM::CallOp>(
loc, ity, lenParams, llvm::ArrayRef<mlir::NamedAttribute>{attr});
size = call.getResult(0);
ty = mlir::LLVM::LLVMPointerType::get(
mlir::IntegerType::get(alloc.getContext(), 8));
} else {
return emitError(loc, "unexpected type ")
<< scalarType << " with type parameters";
}
}
if (alloc.hasShapeOperands()) {
mlir::Type allocEleTy = fir::unwrapRefType(alloc.getType());
// Scale the size by constant factors encoded in the array type.
if (auto seqTy = allocEleTy.dyn_cast<fir::SequenceType>()) {
fir::SequenceType::Extent constSize = 1;
for (auto extent : seqTy.getShape())
if (extent != fir::SequenceType::getUnknownExtent())
constSize *= extent;
mlir::Value constVal{
genConstantIndex(loc, ity, rewriter, constSize).getResult()};
size = rewriter.create<mlir::LLVM::MulOp>(loc, ity, size, constVal);
}
unsigned end = operands.size();
for (; i < end; ++i)
size = rewriter.create<mlir::LLVM::MulOp>(
loc, ity, size, integerCast(loc, rewriter, ity, operands[i]));
}
if (ty == resultTy) {
// Do not emit the bitcast if ty and resultTy are the same.
rewriter.replaceOpWithNewOp<mlir::LLVM::AllocaOp>(alloc, ty, size,
alloc->getAttrs());
} else {
auto al = rewriter.create<mlir::LLVM::AllocaOp>(loc, ty, size,
alloc->getAttrs());
rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(alloc, resultTy, al);
}
return success();
}
};
/// Lower `fir.box_addr` to the sequence of operations to extract the first
/// element of the box.
@ -1070,16 +1185,16 @@ public:
fir::LLVMTypeConverter typeConverter{getModule()};
mlir::OwningRewritePatternList pattern(context);
pattern.insert<
AddcOpConversion, AddrOfOpConversion, BoxAddrOpConversion,
BoxDimsOpConversion, BoxEleSizeOpConversion, BoxIsAllocOpConversion,
BoxIsArrayOpConversion, BoxIsPtrOpConversion, BoxRankOpConversion,
CallOpConversion, ConvertOpConversion, DivcOpConversion,
ExtractValueOpConversion, HasValueOpConversion, GlobalOpConversion,
InsertOnRangeOpConversion, InsertValueOpConversion, LoadOpConversion,
NegcOpConversion, MulcOpConversion, SelectOpConversion,
SelectRankOpConversion, StoreOpConversion, SubcOpConversion,
UndefOpConversion, UnreachableOpConversion, ZeroOpConversion>(
typeConverter);
AddcOpConversion, AddrOfOpConversion, AllocaOpConversion,
BoxAddrOpConversion, BoxDimsOpConversion, BoxEleSizeOpConversion,
BoxIsAllocOpConversion, BoxIsArrayOpConversion, BoxIsPtrOpConversion,
BoxRankOpConversion, CallOpConversion, ConvertOpConversion,
DivcOpConversion, ExtractValueOpConversion, HasValueOpConversion,
GlobalOpConversion, InsertOnRangeOpConversion, InsertValueOpConversion,
LoadOpConversion, NegcOpConversion, MulcOpConversion,
SelectOpConversion, SelectRankOpConversion, StoreOpConversion,
SubcOpConversion, UndefOpConversion, UnreachableOpConversion,
ZeroOpConversion>(typeConverter);
mlir::populateStdToLLVMConversionPatterns(typeConverter, pattern);
mlir::arith::populateArithmeticToLLVMConversionPatterns(typeConverter,
pattern);

View File

@ -93,6 +93,9 @@ public:
// types. Indexing into other aggregate types is more flexible.
mlir::Type offsetType() { return mlir::IntegerType::get(&getContext(), 32); }
// i64 can be used to index into aggregates like arrays
mlir::Type indexType() { return mlir::IntegerType::get(&getContext(), 64); }
// fir.type<name(p : TY'...){f : TY...}> --> llvm<"%name = { ty... }">
mlir::Type convertRecordType(fir::RecordType derived) {
auto name = derived.getName();

View File

@ -57,8 +57,6 @@ static bool verifyInType(mlir::Type inType,
if (verifyInType(field.second, visited))
return true;
visited.pop_back();
} else if (auto rt = inType.dyn_cast<fir::PointerType>()) {
return verifyInType(rt.getEleTy(), visited);
}
return false;
}

View File

@ -329,6 +329,7 @@ func @insert_tuple(%a : tuple<i32, f32>) {
// CHECK: llvm.return
// -----
// Test `fir.call` -> `llvm.call` conversion for functions that take no arguments
// and return nothing
@ -829,3 +830,134 @@ func @box_isptr(%arg0: !fir.box<!fir.array<*:f64>>) -> i1 {
// CHECK: %[[CMP_C0:.*]] = llvm.mlir.constant(0 : i32) : i32
// CHECK: %[[IS_ALLOC:.*]] = llvm.icmp "ne" %[[AND]], %[[CMP_C0]] : i32
// CHECK: llvm.return %[[IS_ALLOC]] : i1
// -----
// Test fir.alloca of one element
func @alloca_one() -> !fir.ref<i32> {
%1 = fir.alloca i32
return %1 : !fir.ref<i32>
}
// CHECK-LABEL: llvm.func @alloca_one() -> !llvm.ptr<i32>
// CHECK: [[N:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK: [[A:%.*]] = llvm.alloca [[N]] x i32
// CHECK: llvm.return [[A]] : !llvm.ptr<i32>
// -----
// Test fir.alloca of several elements
func @alloca_several() -> !fir.ref<i32> {
%0 = arith.constant 100 : index
%1 = fir.alloca i32, %0
return %1 : !fir.ref<i32>
}
// CHECK-LABEL: llvm.func @alloca_several() -> !llvm.ptr<i32>
// CHECK: [[N:%.*]] = llvm.mlir.constant(100 : index) : i64
// CHECK: [[ONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK: [[TOTAL:%.*]] = llvm.mul [[ONE]], [[N]] : i64
// CHECK: [[A:%.*]] = llvm.alloca [[TOTAL]] x i32
// CHECK: llvm.return [[A]] : !llvm.ptr<i32>
// -----
// Test fir.alloca of pointer to array
func @alloca_ptr_to_array() -> !fir.ref<!fir.ptr<!fir.array<?xi32>>> {
%1 = fir.alloca !fir.ptr<!fir.array<?xi32>>
return %1 : !fir.ref<!fir.ptr<!fir.array<?xi32>>>
}
// CHECK-LABEL: llvm.func @alloca_ptr_to_array() -> !llvm.ptr<ptr<i32>>
// CHECK: [[ONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK: [[A:%.*]] = llvm.alloca [[ONE]] x !llvm.ptr<i32>
// CHECK: llvm.return [[A]] : !llvm.ptr<ptr<i32>>
// -----
// Test fir.alloca of char array
func @alloca_char_array(%l: i32, %e : index) -> !fir.ref<!fir.array<?x?x!fir.char<1,?>>> {
%a = fir.alloca !fir.array<?x?x!fir.char<1,?>>(%l : i32), %e, %e
return %a : !fir.ref<!fir.array<?x?x!fir.char<1,?>>>
}
// CHECK-LABEL: llvm.func @alloca_char_array
// CHECK-SAME: ([[L:%.*]]: i32, [[E:%.*]]: i64) -> !llvm.ptr<i8>
// CHECK-DAG: [[UNUSEDONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK-DAG: [[LCAST:%.*]] = llvm.sext [[L]] : i32 to i64
// CHECK-DAG: [[ONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK: [[PROD1:%.*]] = llvm.mul [[LCAST]], [[ONE]] : i64
// CHECK: [[PROD2:%.*]] = llvm.mul [[PROD1]], [[E]] : i64
// CHECK: [[PROD3:%.*]] = llvm.mul [[PROD2]], [[E]] : i64
// CHECK: [[A:%.*]] = llvm.alloca [[PROD3]] x i8 {in_type = !fir.array<?x?x!fir.char<1,?>>
// CHECK: return [[A]] : !llvm.ptr<i8>
// -----
// Test fir.alloca of record type with LEN parameters
// type t(p1,p2)
// integer, len :: p1
// integer(kind=2), len :: p2
// integer f1
// real f2
// end type t
func private @_QTtP.mem.size(%0 : i32, %1 : i16) -> index
func @alloca_record(%arg0 : i32, %arg1 : i16) -> !fir.ref<!fir.type<_QTt(p1:i32,p2:i16){f1:i32,f2:f32}>> {
%0 = fir.alloca !fir.type<_QTt(p1:i32,p2:i16){f1:i32,f2:f32}>(%arg0, %arg1 : i32, i16) {name = "_QEvar"}
return %0 : !fir.ref<!fir.type<_QTt(p1:i32,p2:i16){f1:i32,f2:f32}>>
}
// CHECK-LABEL: llvm.func @alloca_record
// CHECK-SAME: ([[ARG0:%.*]]: i32, [[ARG1:%.*]]: i16)
// CHECK-SAME: -> !llvm.ptr<struct<"_QTt", (i32, f32)>>
// CHECK: [[SIZE:%.*]] = llvm.call @_QTtP.mem.size([[ARG0]], [[ARG1]]) : (i32, i16) -> i64
// CHECK: [[ALLOC:%.*]] = llvm.alloca [[SIZE]] x i8
// CHECK: [[A:%.*]] = llvm.bitcast [[ALLOC]] : !llvm.ptr<i8> to !llvm.ptr<struct<"_QTt", (i32, f32)>>
// CHECK: llvm.return [[A]] : !llvm.ptr<struct<"_QTt", (i32, f32)>>
// -----
// Test fir.alloca of a multidimensional array, with operands
func @alloca_multidim_array(%0 : index) -> !fir.ref<!fir.array<8x16x32xf32>> {
%1 = arith.constant 24 : index
%2 = fir.alloca !fir.array<8x16x32xf32>, %0, %1
return %2 : !fir.ref<!fir.array<8x16x32xf32>>
}
// CHECK-LABEL: llvm.func @alloca_multidim_array
// CHECK-SAME: ([[OP1:%.*]]: i64) -> !llvm.ptr<array<32 x array<16 x array<8 x f32>
// CHECK: [[OP2:%.*]] = llvm.mlir.constant(24 : index) : i64
// CHECK: [[ONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK: [[ALL:%.*]] = llvm.mlir.constant(4096 : i64) : i64
// CHECK: [[MUL1:%.*]] = llvm.mul [[ONE]], [[ALL]] : i64
// CHECK: [[MUL2:%.*]] = llvm.mul [[MUL1]], [[OP1]] : i64
// CHECK: [[TOTAL:%.*]] = llvm.mul [[MUL2]], [[OP2]] : i64
// CHECK: [[A:%.*]] = llvm.alloca [[TOTAL]] x !llvm.array<32 x array<16 x array<8 x f32>
// CHECK: llvm.return [[A]] : !llvm.ptr<array<32 x array<16 x array<8 x f32>
// -----
// Test alloca with an array with holes.
// Constant factor of 60 (4*3*5) must be included.
func @alloca_array_with_holes(%0 : index, %1 : index) -> !fir.ref<!fir.array<4x?x3x?x5xi32>> {
%a = fir.alloca !fir.array<4x?x3x?x5xi32>, %0, %1
return %a : !fir.ref<!fir.array<4x?x3x?x5xi32>>
}
// CHECK-LABEL: llvm.func @alloca_array_with_holes
// CHECK-SAME: ([[A:%.*]]: i64, [[B:%.*]]: i64) -> !llvm.ptr<i32>
// CHECK-DAG: [[ONE:%.*]] = llvm.mlir.constant(1 : i64) : i64
// CHECK-DAG: [[FIXED:%.*]] = llvm.mlir.constant(60 : i64) : i64
// CHECK: [[PROD1:%.*]] = llvm.mul [[ONE]], [[FIXED]] : i64
// CHECK: [[PROD2:%.*]] = llvm.mul [[PROD1]], [[A]] : i64
// CHECK: [[PROD3:%.*]] = llvm.mul [[PROD2]], [[B]] : i64
// CHECK: [[RES:%.*]] = llvm.alloca [[PROD3]] x i32 {in_type = !fir.array<4x?x3x?x5xi32>
// CHECK: llvm.return [[RES]] : !llvm.ptr<i32>