#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"
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;
}
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();
#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"
return func;
auto funTy = getTypeModel<E>()(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;
}
#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"
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;
}
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) {
/*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
/// 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
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;
// Convert ops in target-specific patterns.
mod.walk([&](mlir::Operation *op) {
if (auto call = mlir::dyn_cast<fir::CallOp>(op)) {
- if (!hasPortableSignature(call.getFunctionType()))
+ if (!hasPortableSignature(call.getFunctionType(), op))
convertCallOp(call);
} else if (auto dispatch = mlir::dyn_cast<fir::DispatchOp>(op)) {
- if (!hasPortableSignature(dispatch.getFunctionType()))
+ if (!hasPortableSignature(dispatch.getFunctionType(), op))
convertCallOp(dispatch);
} else if (auto addr = mlir::dyn_cast<fir::AddrOfOp>(op)) {
if (addr.getType().isa<mlir::FunctionType>() &&
- !hasPortableSignature(addr.getType()))
+ !hasPortableSignature(addr.getType(), op))
convertAddrOp(addr);
}
});
/// 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<mlir::FunctionType>());
auto func = signature.dyn_cast<mlir::FunctionType>();
+ bool hasFirRuntime = op->hasAttrOfType<mlir::UnitAttr>(
+ fir::FIROpsDialect::getFirRuntimeAttrName());
for (auto ty : func.getResults())
if ((ty.isa<fir::BoxCharType>() && !noCharacterConversion) ||
- (fir::isa_complex(ty) && !noComplexConversion)) {
+ (fir::isa_complex(ty) && !noComplexConversion) ||
+ (ty.isa<mlir::IntegerType>() && hasFirRuntime)) {
LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n");
return false;
}
for (auto ty : func.getInputs())
if (((ty.isa<fir::BoxCharType>() || fir::isCharacterProcedureTuple(ty)) &&
!noCharacterConversion) ||
- (fir::isa_complex(ty) && !noComplexConversion)) {
+ (fir::isa_complex(ty) && !noComplexConversion) ||
+ (ty.isa<mlir::IntegerType>() && hasFirRuntime)) {
LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n");
return false;
}
/// the immediately subsequent target code gen.
void convertSignature(mlir::func::FuncOp func) {
auto funcTy = func.getFunctionType().cast<mlir::FunctionType>();
- if (hasPortableSignature(funcTy) && !hasHostAssociations(func))
+ if (hasPortableSignature(funcTy, func) && !hasHostAssociations(func))
return;
llvm::SmallVector<mlir::Type> newResTys;
llvm::SmallVector<mlir::Type> newInTys;
llvm::SmallVector<std::pair<unsigned, mlir::NamedAttribute>> savedAttrs;
llvm::SmallVector<std::pair<unsigned, mlir::NamedAttribute>> extraAttrs;
llvm::SmallVector<FixupTy> fixups;
+ llvm::SmallVector<std::pair<unsigned, mlir::NamedAttrList>, 1> resultAttrs;
// Save argument attributes in case there is a shift so we can replace them
// correctly.
else
doComplexReturn(func, cmplx, newResTys, newInTys, fixups);
})
+ .Case<mlir::IntegerType>([&](mlir::IntegerType intTy) {
+ auto m = specifics->integerArgumentType(func.getLoc(), intTy);
+ assert(m.size() == 1);
+ auto attr = std::get<fir::CodeGenSpecifics::Attributes>(m[0]);
+ auto retTy = std::get<mlir::Type>(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<mlir::UnitAttr>(
+ 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
newInTys.push_back(ty);
}
})
+ .Case<mlir::IntegerType>([&](mlir::IntegerType intTy) {
+ auto m = specifics->integerArgumentType(func.getLoc(), intTy);
+ assert(m.size() == 1);
+ auto attr = std::get<fir::CodeGenSpecifics::Attributes>(m[0]);
+ auto argTy = std::get<mlir::Type>(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<mlir::UnitAttr>(
+ 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<mlir::UnitAttr>(index,
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<fir::AllocaOp>(loc, newInTys[fixup.index]);
rewriter->create<fir::StoreOp>(loc, newArg, mem);
- auto oldArgTy =
- fir::ReferenceType::get(oldArgTys[fixup.index - offset]);
auto cast = rewriter->create<fir::ConvertOp>(loc, oldArgTy, mem);
mlir::Value load = rewriter->create<fir::LoadOp>(loc, cast);
func.getArgument(fixup.index + 1).replaceAllUsesWith(load);
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) {
--- /dev/null
+// 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.logical<4>> {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<!fir.char<1,11>>
+ %1 = fir.convert %0 : (!fir.ref<!fir.char<1,11>>) -> !fir.ref<i8>
+ %2 = fir.call @_FortranAioBeginExternalListOutput(%c-1_i32, %1, %c3_i32) : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+ %3 = fir.load %arg0 : !fir.ref<!fir.logical<4>>
+ %4 = fir.convert %3 : (!fir.logical<4>) -> i1
+ %5 = fir.call @_FortranAioOutputLogical(%2, %4) : (!fir.ref<i8>, i1) -> i1
+ %6 = fir.call @_FortranAioEndIoStatement(%2) : (!fir.ref<i8>) -> i32
+ return
+}
+func.func private @_FortranAioBeginExternalListOutput(i32, !fir.ref<i8>, i32) -> !fir.ref<i8> 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<i8>, i1) -> i1 attributes {fir.io, fir.runtime}
+func.func private @_FortranAioEndIoStatement(!fir.ref<i8>) -> 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.logical<4>> {fir.bindc_name = "x"}) {
+ %0 = fir.load %arg0 : !fir.ref<!fir.logical<4>>
+ %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.logical<4>> {fir.bindc_name = "x"}) {
+ %0 = fir.load %arg0 : !fir.ref<!fir.logical<4>>
+ %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}