diff --git a/flang/include/flang/Optimizer/Builder/Runtime/RTBuilder.h b/flang/include/flang/Optimizer/Builder/Runtime/RTBuilder.h --- a/flang/include/flang/Optimizer/Builder/Runtime/RTBuilder.h +++ b/flang/include/flang/Optimizer/Builder/Runtime/RTBuilder.h @@ -20,6 +20,7 @@ #include "flang/Common/Fortran.h" #include "flang/Common/uint128.h" #include "flang/Optimizer/Builder/FIRBuilder.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Dialect/FIRType.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/MLIRContext.h" @@ -411,7 +412,7 @@ return func; auto funTy = RuntimeEntry::getTypeModel()(builder.getContext()); func = builder.createFunction(loc, name, funTy); - func->setAttr("fir.runtime", builder.getUnitAttr()); + func->setAttr(FIROpsDialect::getFirRuntimeAttrName(), builder.getUnitAttr()); return func; } diff --git a/flang/include/flang/Optimizer/Dialect/FIRDialect.h b/flang/include/flang/Optimizer/Dialect/FIRDialect.h --- a/flang/include/flang/Optimizer/Dialect/FIRDialect.h +++ b/flang/include/flang/Optimizer/Dialect/FIRDialect.h @@ -37,6 +37,11 @@ void printAttribute(mlir::Attribute attr, mlir::DialectAsmPrinter &p) const override; + /// Return string name of fir.runtime attribute. + static constexpr llvm::StringRef getFirRuntimeAttrName() { + return "fir.runtime"; + } + private: // Register the Attributes of this dialect. void registerAttributes(); diff --git a/flang/lib/Lower/IO.cpp b/flang/lib/Lower/IO.cpp --- a/flang/lib/Lower/IO.cpp +++ b/flang/lib/Lower/IO.cpp @@ -27,6 +27,7 @@ #include "flang/Optimizer/Builder/FIRBuilder.h" #include "flang/Optimizer/Builder/Runtime/RTBuilder.h" #include "flang/Optimizer/Builder/Todo.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Support/FIRContext.h" #include "flang/Parser/parse-tree.h" #include "flang/Runtime/io-api.h" @@ -167,7 +168,8 @@ return func; auto funTy = getTypeModel()(builder.getContext()); func = builder.createFunction(loc, name, funTy); - func->setAttr("fir.runtime", builder.getUnitAttr()); + func->setAttr(fir::FIROpsDialect::getFirRuntimeAttrName(), + builder.getUnitAttr()); func->setAttr("fir.io", builder.getUnitAttr()); return func; } diff --git a/flang/lib/Lower/IntrinsicCall.cpp b/flang/lib/Lower/IntrinsicCall.cpp --- a/flang/lib/Lower/IntrinsicCall.cpp +++ b/flang/lib/Lower/IntrinsicCall.cpp @@ -32,6 +32,7 @@ #include "flang/Optimizer/Builder/Runtime/Stop.h" #include "flang/Optimizer/Builder/Runtime/Transformational.h" #include "flang/Optimizer/Builder/Todo.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Optimizer/Support/FatalError.h" #include "flang/Runtime/entry-names.h" @@ -1684,7 +1685,8 @@ const RuntimeFunction &runtime) { mlir::func::FuncOp function = builder.addNamedFunction( loc, runtime.symbol, runtime.typeGenerator(builder.getContext())); - function->setAttr("fir.runtime", builder.getUnitAttr()); + function->setAttr(fir::FIROpsDialect::getFirRuntimeAttrName(), + builder.getUnitAttr()); return function; } diff --git a/flang/lib/Optimizer/CodeGen/Target.h b/flang/lib/Optimizer/CodeGen/Target.h --- a/flang/lib/Optimizer/CodeGen/Target.h +++ b/flang/lib/Optimizer/CodeGen/Target.h @@ -29,21 +29,29 @@ /// LLVMContext. class Attributes { public: + enum class IntegerExtension { None, Zero, Sign }; + Attributes(unsigned short alignment = 0, bool byval = false, - bool sret = false, bool append = false) - : alignment{alignment}, byval{byval}, sret{sret}, append{append} {} + bool sret = false, bool append = false, + IntegerExtension intExt = IntegerExtension::None) + : alignment{alignment}, byval{byval}, sret{sret}, append{append}, + intExt{intExt} {} unsigned getAlignment() const { return alignment; } bool hasAlignment() const { return alignment != 0; } bool isByVal() const { return byval; } bool isSRet() const { return sret; } bool isAppend() const { return append; } + bool isZeroExt() const { return intExt == IntegerExtension::Zero; } + bool isSignExt() const { return intExt == IntegerExtension::Sign; } + llvm::StringRef getIntExtensionAttrName() const; private: unsigned short alignment{}; bool byval : 1; bool sret : 1; bool append : 1; + IntegerExtension intExt; }; } // namespace details @@ -94,6 +102,47 @@ virtual Marshalling boxcharArgumentType(mlir::Type eleTy, bool sret = false) const = 0; + // Compute ABI rules for an integer argument of the given mlir::IntegerType + // \p argTy. Note that this methods is supposed to be called for + // arguments passed by value not via reference, e.g. the 'i1' argument here: + // declare i1 @_FortranAioOutputLogical(ptr, i1) + // + // \p loc is the location of the operation using/specifying the argument. + // + // Currently, the only supported marshalling is whether the argument + // should be zero or sign extended. + // + // The zero/sign extension is especially important to comply with the ABI + // used by C/C++ compiler that builds Fortran runtime. As in the above + // example the callee will expect the caller to zero extend the second + // argument up to the size of the C/C++'s 'int' type. + // The corresponding handling in clang is done in + // DefaultABIInfo::classifyArgumentType(), and the logic may brielfy + // be explained as some sort of extension is required if the integer + // type is shorter than the size of 'int' for the target. + // The related code is located in ASTContext::isPromotableIntegerType() + // and ABIInfo::isPromotableIntegerTypeForABI(). + // In particular, the latter returns 'true' for 'bool', several kinds + // of 'char', 'short', 'wchar' and enumerated types. + // The type of the extensions (zero or sign) depends on the signedness + // of the original language type. + // + // It is not clear how to handle signless integer types. + // From the point of Fortran-C interface all supported integer types + // seem to be signed except for CFI_type_Bool/bool that is supported + // via signless 'i1', but that is treated as unsigned type by clang + // (e.g. 'bool' arguments are using 'zeroext' ABI). + virtual Marshalling integerArgumentType(mlir::Location loc, + mlir::IntegerType argTy) const = 0; + + // By default, integer argument and return values use the same + // zero/sign extension rules. + virtual Marshalling integerReturnType(mlir::Location loc, + mlir::IntegerType argTy) const = 0; + + // Returns width in bits of C/C++ 'int' type size. + virtual unsigned char getCIntTypeWidth() const = 0; + protected: mlir::MLIRContext &context; llvm::Triple triple; diff --git a/flang/lib/Optimizer/CodeGen/Target.cpp b/flang/lib/Optimizer/CodeGen/Target.cpp --- a/flang/lib/Optimizer/CodeGen/Target.cpp +++ b/flang/lib/Optimizer/CodeGen/Target.cpp @@ -22,6 +22,19 @@ using namespace fir; +namespace fir::details { +llvm::StringRef Attributes::getIntExtensionAttrName() const { + // The attribute names are available via LLVM dialect interfaces + // like getZExtAttrName(), getByValAttrName(), etc., so we'd better + // use them than literals. + if (isZeroExt()) + return "llvm.zeroext"; + else if (isSignExt()) + return "llvm.signext"; + return {}; +} +} // namespace fir::details + // Reduce a REAL/float type to the floating point semantics. static const llvm::fltSemantics &floatToSemantics(const KindMapping &kindMap, mlir::Type type) { @@ -67,6 +80,46 @@ /*sret=*/sret, /*append=*/!sret}); return marshal; } + + CodeGenSpecifics::Marshalling + integerArgumentType(mlir::Location loc, + mlir::IntegerType argTy) const override { + CodeGenSpecifics::Marshalling marshal; + AT::IntegerExtension intExt = AT::IntegerExtension::None; + if (argTy.getWidth() < getCIntTypeWidth()) { + // isSigned() and isUnsigned() branches below are dead code currently. + // If needed, we can generate calls with signed/unsigned argument types + // to more precisely match C side (e.g. for Fortran runtime functions + // with 'unsigned short' arguments). + if (argTy.isSigned()) + intExt = AT::IntegerExtension::Sign; + else if (argTy.isUnsigned()) + intExt = AT::IntegerExtension::Zero; + else if (argTy.isSignless()) { + // Zero extend for 'i1' and sign extend for other types. + if (argTy.getWidth() == 1) + intExt = AT::IntegerExtension::Zero; + else + intExt = AT::IntegerExtension::Sign; + } + } + + marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false, + /*sret=*/false, /*append=*/false, + /*intExt=*/intExt}); + return marshal; + } + + CodeGenSpecifics::Marshalling + integerReturnType(mlir::Location loc, + mlir::IntegerType argTy) const override { + return integerArgumentType(loc, argTy); + } + + // Width of 'int' type is 32-bits for almost all targets, except + // for AVR and MSP430 (see TargetInfo initializations + // in clang/lib/Basic/Targets). + unsigned char getCIntTypeWidth() const override { return 32; } }; } // namespace diff --git a/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp --- a/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp +++ b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp @@ -100,14 +100,14 @@ // Convert ops in target-specific patterns. mod.walk([&](mlir::Operation *op) { if (auto call = mlir::dyn_cast(op)) { - if (!hasPortableSignature(call.getFunctionType())) + if (!hasPortableSignature(call.getFunctionType(), op)) convertCallOp(call); } else if (auto dispatch = mlir::dyn_cast(op)) { - if (!hasPortableSignature(dispatch.getFunctionType())) + if (!hasPortableSignature(dispatch.getFunctionType(), op)) convertCallOp(dispatch); } else if (auto addr = mlir::dyn_cast(op)) { if (addr.getType().isa() && - !hasPortableSignature(addr.getType())) + !hasPortableSignature(addr.getType(), op)) convertAddrOp(addr); } }); @@ -443,19 +443,23 @@ /// then it is considered portable for any target, and this function will /// return `true`. Otherwise, the signature is not portable and `false` is /// returned. - bool hasPortableSignature(mlir::Type signature) { + bool hasPortableSignature(mlir::Type signature, mlir::Operation *op) { assert(signature.isa()); auto func = signature.dyn_cast(); + bool hasFirRuntime = op->hasAttrOfType( + fir::FIROpsDialect::getFirRuntimeAttrName()); for (auto ty : func.getResults()) if ((ty.isa() && !noCharacterConversion) || - (fir::isa_complex(ty) && !noComplexConversion)) { + (fir::isa_complex(ty) && !noComplexConversion) || + (ty.isa() && hasFirRuntime)) { LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n"); return false; } for (auto ty : func.getInputs()) if (((ty.isa() || fir::isCharacterProcedureTuple(ty)) && !noCharacterConversion) || - (fir::isa_complex(ty) && !noComplexConversion)) { + (fir::isa_complex(ty) && !noComplexConversion) || + (ty.isa() && hasFirRuntime)) { LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n"); return false; } @@ -476,13 +480,14 @@ /// the immediately subsequent target code gen. void convertSignature(mlir::func::FuncOp func) { auto funcTy = func.getFunctionType().cast(); - if (hasPortableSignature(funcTy) && !hasHostAssociations(func)) + if (hasPortableSignature(funcTy, func) && !hasHostAssociations(func)) return; llvm::SmallVector newResTys; llvm::SmallVector newInTys; llvm::SmallVector> savedAttrs; llvm::SmallVector> extraAttrs; llvm::SmallVector fixups; + llvm::SmallVector, 1> resultAttrs; // Save argument attributes in case there is a shift so we can replace them // correctly. @@ -509,6 +514,22 @@ else doComplexReturn(func, cmplx, newResTys, newInTys, fixups); }) + .Case([&](mlir::IntegerType intTy) { + auto m = specifics->integerArgumentType(func.getLoc(), intTy); + assert(m.size() == 1); + auto attr = std::get(m[0]); + auto retTy = std::get(m[0]); + std::size_t resId = newResTys.size(); + llvm::StringRef extensionAttrName = attr.getIntExtensionAttrName(); + if (!extensionAttrName.empty() && + // TODO: we have to do the same for BIND(C) routines. + func->hasAttrOfType( + fir::FIROpsDialect::getFirRuntimeAttrName())) + resultAttrs.emplace_back( + resId, rewriter->getNamedAttr(extensionAttrName, + rewriter->getUnitAttr())); + newResTys.push_back(retTy); + }) .Default([&](mlir::Type ty) { newResTys.push_back(ty); }); // Saved potential shift in argument. Handling of result can add arguments @@ -572,6 +593,26 @@ newInTys.push_back(ty); } }) + .Case([&](mlir::IntegerType intTy) { + auto m = specifics->integerArgumentType(func.getLoc(), intTy); + assert(m.size() == 1); + auto attr = std::get(m[0]); + auto argTy = std::get(m[0]); + auto argNo = newInTys.size(); + llvm::StringRef extensionAttrName = attr.getIntExtensionAttrName(); + if (!extensionAttrName.empty() && + // TODO: we have to do the same for BIND(C) routines. + func->hasAttrOfType( + fir::FIROpsDialect::getFirRuntimeAttrName())) { + fixups.emplace_back(FixupTy::Codes::ArgumentType, argNo, + [=](mlir::func::FuncOp func) { + func.setArgAttr( + argNo, extensionAttrName, + mlir::UnitAttr::get(func.getContext())); + }); + } + newInTys.push_back(argTy); + }) .Default([&](mlir::Type ty) { newInTys.push_back(ty); }); if (func.getArgAttrOfType(index, @@ -608,14 +649,18 @@ case FixupTy::Codes::ArgumentType: { // Argument is pass-by-value, but its type has likely been modified to // suit the target ABI convention. + auto oldArgTy = + fir::ReferenceType::get(oldArgTys[fixup.index - offset]); + // If type did not change, keep the original argument. + if (newInTys[fixup.index] == oldArgTy) + break; + auto newArg = func.front().insertArgument(fixup.index, newInTys[fixup.index], loc); rewriter->setInsertionPointToStart(&func.front()); auto mem = rewriter->create(loc, newInTys[fixup.index]); rewriter->create(loc, newArg, mem); - auto oldArgTy = - fir::ReferenceType::get(oldArgTys[fixup.index - offset]); auto cast = rewriter->create(loc, oldArgTy, mem); mlir::Value load = rewriter->create(loc, cast); func.getArgument(fixup.index + 1).replaceAllUsesWith(load); @@ -744,6 +789,10 @@ func.setArgAttr(extraAttr.first, extraAttr.second.getName(), extraAttr.second.getValue()); + for (auto [resId, resAttrList] : resultAttrs) + for (mlir::NamedAttribute resAttr : resAttrList) + func.setResultAttr(resId, resAttr.getName(), resAttr.getValue()); + // Replace attributes to the correct argument if there was an argument shift // to the right. if (argumentShift > 0) { diff --git a/flang/test/Fir/target-rewrite-integer.fir b/flang/test/Fir/target-rewrite-integer.fir new file mode 100644 --- /dev/null +++ b/flang/test/Fir/target-rewrite-integer.fir @@ -0,0 +1,77 @@ +// RUN: fir-opt --split-input-file --target-rewrite="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=I32,ALL +// RUN: fir-opt --split-input-file --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=X64,ALL +// RUN: fir-opt --split-input-file --target-rewrite="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=AARCH64,ALL +// RUN: fir-opt --split-input-file --target-rewrite="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=PPC,ALL +// RUN: fir-opt --split-input-file --target-rewrite="target=sparc64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=SPARCV9,ALL +// RUN: fir-opt --split-input-file --target-rewrite="target=sparcv9-sun-solaris2.11" %s | FileCheck %s --check-prefixes=SPARCV9,ALL + +// ----- + +// subroutine test_i1(x) +// logical x +// print *, x +// end subroutine test_i1 + +// ALL-LABEL: @_QPtest_i1 +// I32: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext}) +// X64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext}) +// AARCH64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext}) +// PPC: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext}) +// SPARCV9: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext}) +func.func @_QPtest_i1(%arg0: !fir.ref> {fir.bindc_name = "x"}) { + %c3_i32 = arith.constant 3 : i32 + %c-1_i32 = arith.constant -1 : i32 + %0 = fir.address_of(@_QQcl.2E2F746573742E66393000) : !fir.ref> + %1 = fir.convert %0 : (!fir.ref>) -> !fir.ref + %2 = fir.call @_FortranAioBeginExternalListOutput(%c-1_i32, %1, %c3_i32) : (i32, !fir.ref, i32) -> !fir.ref + %3 = fir.load %arg0 : !fir.ref> + %4 = fir.convert %3 : (!fir.logical<4>) -> i1 + %5 = fir.call @_FortranAioOutputLogical(%2, %4) : (!fir.ref, i1) -> i1 + %6 = fir.call @_FortranAioEndIoStatement(%2) : (!fir.ref) -> i32 + return +} +func.func private @_FortranAioBeginExternalListOutput(i32, !fir.ref, i32) -> !fir.ref attributes {fir.io, fir.runtime} +fir.global linkonce @_QQcl.2E2F746573742E66393000 constant : !fir.char<1,11> { + %0 = fir.string_lit "./test.f90\00"(11) : !fir.char<1,11> + fir.has_value %0 : !fir.char<1,11> +} +func.func private @_FortranAioOutputLogical(!fir.ref, i1) -> i1 attributes {fir.io, fir.runtime} +func.func private @_FortranAioEndIoStatement(!fir.ref) -> i32 attributes {fir.io, fir.runtime} + +// ----- + +// Manually created test with 'si1' argument/return type. +// Flang does not use 'si1' type currently. + +// ALL-LABEL: @_QPtest_si1 +// I32: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext}) +// X64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext}) +// AARCH64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext}) +// PPC: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext}) +// SPARCV9: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext}) +func.func @_QPtest_si1(%arg0: !fir.ref> {fir.bindc_name = "x"}) { + %0 = fir.load %arg0 : !fir.ref> + %1 = fir.convert %0 : (!fir.logical<4>) -> si1 + %2 = fir.call @_SomeFunc_si1(%1) : (si1) -> si1 + return +} +func.func private @_SomeFunc_si1(si1) -> si1 attributes {fir.runtime} + +// ----- + +// Manually created test with 'ui1' argument/return type. +// Flang does not use 'ui1' type currently. + +// ALL-LABEL: @_QPtest_ui1 +// I32: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext}) +// X64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext}) +// AARCH64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext}) +// PPC: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext}) +// SPARCV9: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext}) +func.func @_QPtest_ui1(%arg0: !fir.ref> {fir.bindc_name = "x"}) { + %0 = fir.load %arg0 : !fir.ref> + %1 = fir.convert %0 : (!fir.logical<4>) -> ui1 + %2 = fir.call @_SomeFunc_ui1(%1) : (ui1) -> ui1 + return +} +func.func private @_SomeFunc_ui1(ui1) -> ui1 attributes {fir.runtime}