diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp --- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp +++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp @@ -28,6 +28,14 @@ // fir::LLVMTypeConverter for converting to LLVM IR dialect types. #include "TypeConverter.h" +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(loc, ity, cattr); +} + namespace { /// FIR conversion pattern template template @@ -65,6 +73,34 @@ mlir::ConversionPatternRewriter &rewriter) const = 0; }; +// Create value signaling an absent optional argument in a call, e.g. +// `fir.absent !fir.ref` --> `llvm.mlir.null : !llvm.ptr` +struct AbsentOpConversion : public FIROpConversion { + using FIROpConversion::FIROpConversion; + + mlir::LogicalResult + matchAndRewrite(fir::AbsentOp absent, OpAdaptor, + mlir::ConversionPatternRewriter &rewriter) const override { + mlir::Type ty = convertType(absent.getType()); + mlir::Location loc = absent.getLoc(); + + if (absent.getType().isa()) { + auto structTy = ty.cast(); + assert(!structTy.isOpaque() && !structTy.getBody().empty()); + auto undefStruct = rewriter.create(loc, ty); + auto nullField = + rewriter.create(loc, structTy.getBody()[0]); + mlir::MLIRContext *ctx = absent.getContext(); + auto c0 = mlir::ArrayAttr::get(ctx, rewriter.getI32IntegerAttr(0)); + rewriter.replaceOpWithNewOp( + absent, ty, undefStruct, nullField, c0); + } else { + rewriter.replaceOpWithNewOp(absent, ty); + } + return success(); + } +}; + // Lower `fir.address_of` operation to `llvm.address_of` operation. struct AddrOfOpConversion : public FIROpConversion { using FIROpConversion::FIROpConversion; @@ -487,6 +523,41 @@ return success(); } }; + +// `fir.is_present` --> +// ``` +// %0 = llvm.mlir.constant(0 : i64) +// %1 = llvm.ptrtoint %0 +// %2 = llvm.icmp "ne" %1, %0 : i64 +// ``` +struct IsPresentOpConversion : public FIROpConversion { + using FIROpConversion::FIROpConversion; + + mlir::LogicalResult + matchAndRewrite(fir::IsPresentOp isPresent, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const override { + mlir::Type idxTy = lowerTy().indexType(); + mlir::Location loc = isPresent.getLoc(); + auto ptr = adaptor.getOperands()[0]; + + if (isPresent.val().getType().isa()) { + auto structTy = ptr.getType().cast(); + assert(!structTy.isOpaque() && !structTy.getBody().empty()); + + mlir::Type ty = structTy.getBody()[0]; + mlir::MLIRContext *ctx = isPresent.getContext(); + auto c0 = mlir::ArrayAttr::get(ctx, rewriter.getI32IntegerAttr(0)); + ptr = rewriter.create(loc, ty, ptr, c0); + } + mlir::LLVM::ConstantOp c0 = + genConstantIndex(isPresent.getLoc(), idxTy, rewriter, 0); + auto addr = rewriter.create(loc, idxTy, ptr); + rewriter.replaceOpWithNewOp( + isPresent, mlir::LLVM::ICmpPredicate::ne, addr, c0); + + return success(); + } +}; } // namespace namespace { @@ -504,12 +575,13 @@ auto *context = getModule().getContext(); fir::LLVMTypeConverter typeConverter{getModule()}; mlir::OwningRewritePatternList pattern(context); - pattern.insert< - AddrOfOpConversion, CallOpConversion, ExtractValueOpConversion, - HasValueOpConversion, GlobalOpConversion, InsertOnRangeOpConversion, - InsertValueOpConversion, SelectOpConversion, SelectRankOpConversion, - UndefOpConversion, UnreachableOpConversion, ZeroOpConversion>( - typeConverter); + pattern + .insert(typeConverter); mlir::populateStdToLLVMConversionPatterns(typeConverter, pattern); mlir::arith::populateArithmeticToLLVMConversionPatterns(typeConverter, pattern); diff --git a/flang/lib/Optimizer/CodeGen/TypeConverter.h b/flang/lib/Optimizer/CodeGen/TypeConverter.h --- a/flang/lib/Optimizer/CodeGen/TypeConverter.h +++ b/flang/lib/Optimizer/CodeGen/TypeConverter.h @@ -48,6 +48,9 @@ }); } + // i64 can be used to index into aggregates like arrays + mlir::Type indexType() { return mlir::IntegerType::get(&getContext(), 64); } + // fir.type --> llvm<"%name = { ty... }"> mlir::Type convertRecordType(fir::RecordType derived) { auto name = derived.getName(); diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir --- a/flang/test/Fir/convert-to-llvm.fir +++ b/flang/test/Fir/convert-to-llvm.fir @@ -325,6 +325,7 @@ // CHECK: llvm.return // ----- + // Test `fir.call` -> `llvm.call` conversion for functions that take no arguments // and return nothing @@ -376,3 +377,88 @@ // CHECK-NEXT: %0 = llvm.call @dummy_return_val() : () -> i32 // CHECK-NEXT: llvm.return %0 : i32 // CHECK-NEXT: } + +// ----- + +// Test `fir.is_present` + +func @test_is_present_i64(%arg0: !fir.ref) -> () { + %0 = fir.is_present %arg0 : (!fir.ref) -> i1 + return +} + +// CHECK-LABEL: @test_is_present_i64 +// CHECK-SAME: (%[[arg:.*]]: !llvm.ptr) +// CHECK-NEXT: %[[constant:.*]] = llvm.mlir.constant(0 : i64) : i64 +// CHECK-NEXT: %[[ptr:.*]] = llvm.ptrtoint %[[arg]] : !llvm.ptr to i64 +// CHECK-NEXT: %{{.*}} = llvm.icmp "ne" %[[ptr]], %[[constant]] : i64 +// CHECK-NEXT: llvm.return +// CHECK-NEXT: } + +func @test_is_present_box(%arg0: !fir.box>) -> () { + %0 = fir.is_present %arg0 : (!fir.box>) -> i1 + return +} + +// CHECK-LABEL: @test_is_present_box +// CHECK-SAME: (%[[arg:.*]]: !llvm.ptr, i64, i32, i8, i8, i8, i8)>>) +// CHECK-NEXT: %[[constant:.*]] = llvm.mlir.constant(0 : i64) : i64 +// CHECK-NEXT: %[[ptr:.*]] = llvm.ptrtoint %[[arg]] : !llvm.ptr, i64, i32, i8, i8, i8, i8)>> to i64 +// CHECK-NEXT: %{{.*}} = llvm.icmp "ne" %[[ptr]], %[[constant]] : i64 +// CHECK-NEXT: llvm.return +// CHECK-NEXT: } + + +// ----- + +// Test `fir.absent` + +func @test_absent_i64() -> () { + %0 = fir.absent !fir.ref + return +} + +// CHECK-LABEL: @test_absent_i64 +// CHECK-NEXT: %{{.*}} = llvm.mlir.null : !llvm.ptr +// CHECK-NEXT: llvm.return +// CHECK-NEXT: } + +func @test_absent_box() -> () { + %0 = fir.absent !fir.box> + return +} +// CHECK-LABEL: @test_absent_box +// CHECK-NEXT: %{{.*}} = llvm.mlir.null : !llvm.ptr, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>> +// CHECK-NEXT: llvm.return +// CHECK-NEXT: } + +// ----- + +// This is a bit more comprehensive test for `fir.is_present` and `fir.absent` +// when used together + +func @is_present(%arg0: !fir.ref) -> i1 { + %0 = fir.is_present %arg0 : (!fir.ref) -> i1 + return %0 : i1 +} + +// CHECK-LABEL: @is_present +// CHECK-SAME: (%[[arg:.*]]: !llvm.ptr) -> i1 +// CHECK-NEXT: %[[constant:.*]] = llvm.mlir.constant(0 : i64) : i64 +// CHECK-NEXT: %[[ptr:.*]] = llvm.ptrtoint %[[arg]] : !llvm.ptr to i64 +// CHECK-NEXT: %[[ret_val:.*]] = llvm.icmp "ne" %[[ptr]], %[[constant]] : i64 +// CHECK-NEXT: llvm.return %[[ret_val]] : i1 +// CHECK-NEXT: } + +func @absent() -> i1 { + %0 = fir.absent !fir.ref + %1 = fir.call @is_present(%0) : (!fir.ref) -> i1 + return %1 : i1 +} + +// CHECK-LABEL: @absent +// CHECK-SAME: () -> i1 +// CHECK-NEXT: %[[ptr:.*]] = llvm.mlir.null : !llvm.ptr +// CHECK-NEXT: %[[ret_val:.*]] = llvm.call @is_present(%[[ptr]]) : (!llvm.ptr) -> i1 +// CHECK-NEXT: llvm.return %[[ret_val]] : i1 +// CHECK-NEXT: }