| //===-- Target.cpp --------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "flang/Optimizer/CodeGen/Target.h" | |
| #include "flang/Optimizer/Builder/Todo.h" | |
| #include "flang/Optimizer/Dialect/FIRType.h" | |
| #include "flang/Optimizer/Dialect/Support/KindMapping.h" | |
| #include "mlir/IR/BuiltinTypes.h" | |
| #include "mlir/IR/TypeRange.h" | |
| #include "llvm/ADT/TypeSwitch.h" | |
| #include <cmath> | |
| #define DEBUG_TYPE "flang-codegen-target" | |
| 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) { | |
| assert(isa_real(type)); | |
| return mlir::cast<mlir::FloatType>(type).getFloatSemantics(); | |
| } | |
| static void typeTodo(const llvm::fltSemantics *sem, mlir::Location loc, | |
| const std::string &context) { | |
| if (sem == &llvm::APFloat::IEEEhalf()) { | |
| TODO(loc, "COMPLEX(KIND=2): for " + context + " type"); | |
| } else if (sem == &llvm::APFloat::BFloat()) { | |
| TODO(loc, "COMPLEX(KIND=3): " + context + " type"); | |
| } else if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| TODO(loc, "COMPLEX(KIND=10): " + context + " type"); | |
| } else { | |
| TODO(loc, "complex for this precision for " + context + " type"); | |
| } | |
| } | |
| namespace { | |
| template <typename S> | |
| struct GenericTarget : public CodeGenSpecifics { | |
| using CodeGenSpecifics::CodeGenSpecifics; | |
| using AT = CodeGenSpecifics::Attributes; | |
| mlir::Type complexMemoryType(mlir::Type eleTy) const override { | |
| assert(fir::isa_real(eleTy)); | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy | |
| return mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}); | |
| } | |
| mlir::Type boxcharMemoryType(mlir::Type eleTy) const override { | |
| auto idxTy = mlir::IntegerType::get(eleTy.getContext(), S::defaultWidth); | |
| auto ptrTy = fir::ReferenceType::get(eleTy); | |
| // Use a type that will be translated into LLVM as: | |
| // { t*, index } | |
| return mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{ptrTy, idxTy}); | |
| } | |
| Marshalling boxcharArgumentType(mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto idxTy = mlir::IntegerType::get(eleTy.getContext(), S::defaultWidth); | |
| auto ptrTy = fir::ReferenceType::get(eleTy); | |
| marshal.emplace_back(ptrTy, AT{}); | |
| // Characters are passed in a split format with all pointers first (in the | |
| // declared position) and all LEN arguments appended after all of the dummy | |
| // arguments. | |
| // NB: Other conventions/ABIs can/should be supported via options. | |
| marshal.emplace_back(idxTy, AT{/*alignment=*/0, /*byval=*/false, | |
| /*sret=*/false, /*append=*/true}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType, | |
| const Marshalling &) const override { | |
| TODO(loc, "passing VALUE BIND(C) derived type for this target"); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType ty) const override { | |
| TODO(loc, "returning BIND(C) derived type for this target"); | |
| } | |
| 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 | |
| //===----------------------------------------------------------------------===// | |
| // i386 (x86 32 bit) linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetI386 : public GenericTarget<TargetI386> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 32; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location, mlir::Type eleTy) const override { | |
| assert(fir::isa_real(eleTy)); | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byval, align 4 | |
| auto structTy = | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}); | |
| marshal.emplace_back(fir::ReferenceType::get(structTy), | |
| AT{/*alignment=*/4, /*byval=*/true}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| assert(fir::isa_real(eleTy)); | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // i64 pack both floats in a 64-bit GPR | |
| marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, sret, align 4 | |
| auto structTy = mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}); | |
| marshal.emplace_back(fir::ReferenceType::get(structTy), | |
| AT{/*alignment=*/4, /*byval=*/false, /*sret=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // i386 (x86 32 bit) Windows target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetI386Win : public GenericTarget<TargetI386Win> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 32; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byval, align 4 | |
| auto structTy = | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}); | |
| marshal.emplace_back(fir::ReferenceType::get(structTy), | |
| AT{/*align=*/4, /*byval=*/true}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // i64 pack both floats in a 64-bit GPR | |
| marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { double, double } struct of 2 double, sret, align 8 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/8, /*byval=*/false, /*sret=*/true}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, sret, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/false, /*sret=*/true}); | |
| } else if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { x86_fp80, x86_fp80 } struct of 2 x86_fp80, sret, align 4 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/4, /*byval=*/false, /*sret=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // x86_64 (x86 64 bit) linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetX86_64 : public GenericTarget<TargetX86_64> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // <2 x t> vector of 2 eleTy | |
| marshal.emplace_back(fir::VectorType::get(2, eleTy), AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // FIXME: In case of SSE register exhaustion, the ABI here may be | |
| // incorrect since LLVM may pass the real via register and the imaginary | |
| // part via the stack while the ABI it should be all in register or all | |
| // in memory. Register occupancy must be analyzed here. | |
| // two distinct double arguments | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| } else if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { x86_fp80, x86_fp80 } struct of 2 fp128, byval, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/true}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, byval, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // <2 x t> vector of 2 eleTy | |
| marshal.emplace_back(fir::VectorType::get(2, eleTy), AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { double, double } struct of 2 double | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| // { x86_fp80, x86_fp80 } | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, sret, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/false, /*sret=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| /// X86-64 argument classes from System V ABI version 1.0 section 3.2.3. | |
| enum ArgClass { | |
| Integer = 0, | |
| SSE, | |
| SSEUp, | |
| X87, | |
| X87Up, | |
| ComplexX87, | |
| NoClass, | |
| Memory | |
| }; | |
| /// Classify an argument type or a field of an aggregate type argument. | |
| /// See System V ABI version 1.0 section 3.2.3. | |
| /// The Lo and Hi class are set to the class of the lower eight eightbytes | |
| /// and upper eight eightbytes on return. | |
| /// If this is called for an aggregate field, the caller is responsible to | |
| /// do the post-merge. | |
| void classify(mlir::Location loc, mlir::Type type, std::uint64_t byteOffset, | |
| ArgClass &Lo, ArgClass &Hi) const { | |
| Hi = Lo = ArgClass::NoClass; | |
| ArgClass ¤t = byteOffset < 8 ? Lo : Hi; | |
| // System V AMD64 ABI 3.2.3. version 1.0 | |
| llvm::TypeSwitch<mlir::Type>(type) | |
| .Case([&](mlir::IntegerType intTy) { | |
| if (intTy.getWidth() == 128) | |
| Hi = Lo = ArgClass::Integer; | |
| else | |
| current = ArgClass::Integer; | |
| }) | |
| .template Case<mlir::FloatType>([&](mlir::Type floatTy) { | |
| const auto *sem = &floatToSemantics(kindMap, floatTy); | |
| if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| Lo = ArgClass::X87; | |
| Hi = ArgClass::X87Up; | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| Lo = ArgClass::SSE; | |
| Hi = ArgClass::SSEUp; | |
| } else { | |
| current = ArgClass::SSE; | |
| } | |
| }) | |
| .Case([&](mlir::ComplexType cmplx) { | |
| const auto *sem = &floatToSemantics(kindMap, cmplx.getElementType()); | |
| if (sem == &llvm::APFloat::x87DoubleExtended()) { | |
| current = ArgClass::ComplexX87; | |
| } else { | |
| fir::SequenceType::Shape shape{2}; | |
| classifyArray(loc, | |
| fir::SequenceType::get(shape, cmplx.getElementType()), | |
| byteOffset, Lo, Hi); | |
| } | |
| }) | |
| .Case([&](fir::LogicalType logical) { | |
| if (kindMap.getLogicalBitsize(logical.getFKind()) == 128) | |
| Hi = Lo = ArgClass::Integer; | |
| else | |
| current = ArgClass::Integer; | |
| }) | |
| .Case( | |
| [&](fir::CharacterType character) { current = ArgClass::Integer; }) | |
| .Case([&](fir::SequenceType seqTy) { | |
| // Array component. | |
| classifyArray(loc, seqTy, byteOffset, Lo, Hi); | |
| }) | |
| .Case([&](fir::RecordType recTy) { | |
| // Component that is a derived type. | |
| classifyStruct(loc, recTy, byteOffset, Lo, Hi); | |
| }) | |
| .Case([&](fir::VectorType vecTy) { | |
| // Previously marshalled SSE eight byte for a previous struct | |
| // argument. | |
| auto *sem = fir::isa_real(vecTy.getEleTy()) | |
| ? &floatToSemantics(kindMap, vecTy.getEleTy()) | |
| : nullptr; | |
| // Not expecting to hit this todo in standard code (it would | |
| // require some vector type extension). | |
| if (!(sem == &llvm::APFloat::IEEEsingle() && vecTy.getLen() <= 2) && | |
| !(sem == &llvm::APFloat::IEEEhalf() && vecTy.getLen() <= 4)) | |
| TODO(loc, "passing vector argument to C by value"); | |
| current = SSE; | |
| }) | |
| .Default([&](mlir::Type ty) { | |
| if (fir::conformsWithPassByRef(ty)) | |
| current = ArgClass::Integer; // Pointers. | |
| else | |
| TODO(loc, "unsupported component type for BIND(C), VALUE derived " | |
| "type argument"); | |
| }); | |
| } | |
| // Classify fields of a derived type starting at \p offset. Returns the new | |
| // offset. Post-merge is left to the caller. | |
| std::uint64_t classifyStruct(mlir::Location loc, fir::RecordType recTy, | |
| std::uint64_t byteOffset, ArgClass &Lo, | |
| ArgClass &Hi) const { | |
| for (auto component : recTy.getTypeList()) { | |
| if (byteOffset > 16) { | |
| // See 3.2.3 p. 1 and note 15. Note that when the offset is bigger | |
| // than 16 bytes here, it is not a single _m256 and or _m512 entity | |
| // that could fit in AVX registers. | |
| Lo = Hi = ArgClass::Memory; | |
| return byteOffset; | |
| } | |
| mlir::Type compType = component.second; | |
| auto [compSize, compAlign] = fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, compType, getDataLayout(), kindMap); | |
| byteOffset = llvm::alignTo(byteOffset, compAlign); | |
| ArgClass LoComp, HiComp; | |
| classify(loc, compType, byteOffset, LoComp, HiComp); | |
| Lo = mergeClass(Lo, LoComp); | |
| Hi = mergeClass(Hi, HiComp); | |
| byteOffset = byteOffset + llvm::alignTo(compSize, compAlign); | |
| if (Lo == ArgClass::Memory || Hi == ArgClass::Memory) | |
| return byteOffset; | |
| } | |
| return byteOffset; | |
| } | |
| // Classify fields of a constant size array type starting at \p offset. | |
| // Returns the new offset. Post-merge is left to the caller. | |
| void classifyArray(mlir::Location loc, fir::SequenceType seqTy, | |
| std::uint64_t byteOffset, ArgClass &Lo, | |
| ArgClass &Hi) const { | |
| mlir::Type eleTy = seqTy.getEleTy(); | |
| const std::uint64_t arraySize = seqTy.getConstantArraySize(); | |
| auto [eleSize, eleAlign] = fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, eleTy, getDataLayout(), kindMap); | |
| std::uint64_t eleStorageSize = llvm::alignTo(eleSize, eleAlign); | |
| for (std::uint64_t i = 0; i < arraySize; ++i) { | |
| byteOffset = llvm::alignTo(byteOffset, eleAlign); | |
| if (byteOffset > 16) { | |
| // See 3.2.3 p. 1 and note 15. Same as in classifyStruct. | |
| Lo = Hi = ArgClass::Memory; | |
| return; | |
| } | |
| ArgClass LoComp, HiComp; | |
| classify(loc, eleTy, byteOffset, LoComp, HiComp); | |
| Lo = mergeClass(Lo, LoComp); | |
| Hi = mergeClass(Hi, HiComp); | |
| byteOffset = byteOffset + eleStorageSize; | |
| if (Lo == ArgClass::Memory || Hi == ArgClass::Memory) | |
| return; | |
| } | |
| } | |
| // Goes through the previously marshalled arguments and count the | |
| // register occupancy to check if there are enough registers left. | |
| bool hasEnoughRegisters(mlir::Location loc, int neededIntRegisters, | |
| int neededSSERegisters, | |
| const Marshalling &previousArguments) const { | |
| int availIntRegisters = 6; | |
| int availSSERegisters = 8; | |
| for (auto typeAndAttr : previousArguments) { | |
| const auto &attr = std::get<Attributes>(typeAndAttr); | |
| if (attr.isByVal()) | |
| continue; // Previous argument passed on the stack. | |
| ArgClass Lo, Hi; | |
| Lo = Hi = ArgClass::NoClass; | |
| classify(loc, std::get<mlir::Type>(typeAndAttr), 0, Lo, Hi); | |
| // post merge is not needed here since previous aggregate arguments | |
| // were marshalled into simpler arguments. | |
| if (Lo == ArgClass::Integer) | |
| --availIntRegisters; | |
| else if (Lo == SSE) | |
| --availSSERegisters; | |
| if (Hi == ArgClass::Integer) | |
| --availIntRegisters; | |
| else if (Hi == ArgClass::SSE) | |
| --availSSERegisters; | |
| } | |
| return availSSERegisters >= neededSSERegisters && | |
| availIntRegisters >= neededIntRegisters; | |
| } | |
| /// Argument class merging as described in System V ABI 3.2.3 point 4. | |
| ArgClass mergeClass(ArgClass accum, ArgClass field) const { | |
| assert((accum != ArgClass::Memory && accum != ArgClass::ComplexX87) && | |
| "Invalid accumulated classification during merge."); | |
| if (accum == field || field == NoClass) | |
| return accum; | |
| if (field == ArgClass::Memory) | |
| return ArgClass::Memory; | |
| if (accum == NoClass) | |
| return field; | |
| if (accum == Integer || field == Integer) | |
| return ArgClass::Integer; | |
| if (field == ArgClass::X87 || field == ArgClass::X87Up || | |
| field == ArgClass::ComplexX87 || accum == ArgClass::X87 || | |
| accum == ArgClass::X87Up) | |
| return Memory; | |
| return SSE; | |
| } | |
| /// Argument class post merging as described in System V ABI 3.2.3 point 5. | |
| void postMerge(std::uint64_t byteSize, ArgClass &Lo, ArgClass &Hi) const { | |
| if (Hi == ArgClass::Memory) | |
| Lo = ArgClass::Memory; | |
| if (Hi == ArgClass::X87Up && Lo != ArgClass::X87) | |
| Lo = ArgClass::Memory; | |
| if (byteSize > 16 && (Lo != ArgClass::SSE || Hi != ArgClass::SSEUp)) | |
| Lo = ArgClass::Memory; | |
| if (Hi == ArgClass::SSEUp && Lo != ArgClass::SSE) | |
| Hi = SSE; | |
| } | |
| /// When \p recTy is a one field record type that can be passed | |
| /// like the field on its own, returns the field type. Returns | |
| /// a null type otherwise. | |
| mlir::Type passAsFieldIfOneFieldStruct(fir::RecordType recTy, | |
| bool allowComplex = false) const { | |
| auto typeList = recTy.getTypeList(); | |
| if (typeList.size() != 1) | |
| return {}; | |
| mlir::Type fieldType = typeList[0].second; | |
| if (mlir::isa<mlir::FloatType, mlir::IntegerType, fir::LogicalType>( | |
| fieldType)) | |
| return fieldType; | |
| if (allowComplex && mlir::isa<mlir::ComplexType>(fieldType)) | |
| return fieldType; | |
| if (mlir::isa<fir::CharacterType>(fieldType)) { | |
| // Only CHARACTER(1) are expected in BIND(C) contexts, which is the only | |
| // contexts where derived type may be passed in registers. | |
| assert(mlir::cast<fir::CharacterType>(fieldType).getLen() == 1 && | |
| "fir.type value arg character components must have length 1"); | |
| return fieldType; | |
| } | |
| // Complex field that needs to be split, or array. | |
| return {}; | |
| } | |
| mlir::Type pickLLVMArgType(mlir::Location loc, mlir::MLIRContext *context, | |
| ArgClass argClass, | |
| std::uint64_t partByteSize) const { | |
| if (argClass == ArgClass::SSE) { | |
| if (partByteSize > 16) | |
| TODO(loc, "passing struct as a real > 128 bits in register"); | |
| // Clang uses vector type when several fp fields are marshalled | |
| // into a single SSE register (like <n x smallest fp field> ). | |
| // It should make no difference from an ABI point of view to just | |
| // select an fp type of the right size, and it makes things simpler | |
| // here. | |
| if (partByteSize > 8) | |
| return mlir::Float128Type::get(context); | |
| if (partByteSize > 4) | |
| return mlir::Float64Type::get(context); | |
| if (partByteSize > 2) | |
| return mlir::Float32Type::get(context); | |
| return mlir::Float16Type::get(context); | |
| } | |
| assert(partByteSize <= 8 && | |
| "expect integer part of aggregate argument to fit into eight bytes"); | |
| if (partByteSize > 4) | |
| return mlir::IntegerType::get(context, 64); | |
| if (partByteSize > 2) | |
| return mlir::IntegerType::get(context, 32); | |
| if (partByteSize > 1) | |
| return mlir::IntegerType::get(context, 16); | |
| return mlir::IntegerType::get(context, 8); | |
| } | |
| /// Marshal a derived type passed by value like a C struct. | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType recTy, | |
| const Marshalling &previousArguments) const override { | |
| std::uint64_t byteOffset = 0; | |
| ArgClass Lo, Hi; | |
| Lo = Hi = ArgClass::NoClass; | |
| byteOffset = classifyStruct(loc, recTy, byteOffset, Lo, Hi); | |
| postMerge(byteOffset, Lo, Hi); | |
| if (Lo == ArgClass::Memory || Lo == ArgClass::X87 || | |
| Lo == ArgClass::ComplexX87) | |
| return passOnTheStack(loc, recTy, /*isResult=*/false); | |
| int neededIntRegisters = 0; | |
| int neededSSERegisters = 0; | |
| if (Lo == ArgClass::SSE) | |
| ++neededSSERegisters; | |
| else if (Lo == ArgClass::Integer) | |
| ++neededIntRegisters; | |
| if (Hi == ArgClass::SSE) | |
| ++neededSSERegisters; | |
| else if (Hi == ArgClass::Integer) | |
| ++neededIntRegisters; | |
| // C struct should not be split into LLVM registers if LLVM codegen is not | |
| // able to later assign actual registers to all of them (struct passing is | |
| // all in registers or all on the stack). | |
| if (!hasEnoughRegisters(loc, neededIntRegisters, neededSSERegisters, | |
| previousArguments)) | |
| return passOnTheStack(loc, recTy, /*isResult=*/false); | |
| if (auto fieldType = passAsFieldIfOneFieldStruct(recTy)) { | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(fieldType, AT{}); | |
| return marshal; | |
| } | |
| if (Hi == ArgClass::NoClass || Hi == ArgClass::SSEUp) { | |
| // Pass a single integer or floating point argument. | |
| mlir::Type lowType = | |
| pickLLVMArgType(loc, recTy.getContext(), Lo, byteOffset); | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(lowType, AT{}); | |
| return marshal; | |
| } | |
| // Split into two integer or floating point arguments. | |
| // Note that for the first argument, this will always pick i64 or f64 which | |
| // may be bigger than needed if some struct padding ends the first eight | |
| // byte (e.g. for `{i32, f64}`). It is valid from an X86-64 ABI and | |
| // semantic point of view, but it may not match the LLVM IR interface clang | |
| // would produce for the equivalent C code (the assembly will still be | |
| // compatible). This allows keeping the logic simpler here since it | |
| // avoids computing the "data" size of the Lo part. | |
| mlir::Type lowType = pickLLVMArgType(loc, recTy.getContext(), Lo, 8u); | |
| mlir::Type hiType = | |
| pickLLVMArgType(loc, recTy.getContext(), Hi, byteOffset - 8u); | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(lowType, AT{}); | |
| marshal.emplace_back(hiType, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType recTy) const override { | |
| std::uint64_t byteOffset = 0; | |
| ArgClass Lo, Hi; | |
| Lo = Hi = ArgClass::NoClass; | |
| byteOffset = classifyStruct(loc, recTy, byteOffset, Lo, Hi); | |
| mlir::MLIRContext *context = recTy.getContext(); | |
| postMerge(byteOffset, Lo, Hi); | |
| if (Lo == ArgClass::Memory) | |
| return passOnTheStack(loc, recTy, /*isResult=*/true); | |
| // Note that X87/ComplexX87 are passed in memory, but returned via %st0 | |
| // %st1 registers. Here, they are returned as fp80 or {fp80, fp80} by | |
| // passAsFieldIfOneFieldStruct, and LLVM will use the expected registers. | |
| // Note that {_Complex long double} is not 100% clear from an ABI | |
| // perspective because the aggregate post merger rules say it should be | |
| // passed in memory because it is bigger than 2 eight bytes. This has the | |
| // funny effect of | |
| // {_Complex long double} return to be dealt with differently than | |
| // _Complex long double. | |
| if (auto fieldType = | |
| passAsFieldIfOneFieldStruct(recTy, /*allowComplex=*/true)) { | |
| if (auto complexType = mlir::dyn_cast<mlir::ComplexType>(fieldType)) | |
| return complexReturnType(loc, complexType.getElementType()); | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(fieldType, AT{}); | |
| return marshal; | |
| } | |
| if (Hi == ArgClass::NoClass || Hi == ArgClass::SSEUp) { | |
| // Return a single integer or floating point argument. | |
| mlir::Type lowType = pickLLVMArgType(loc, context, Lo, byteOffset); | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(lowType, AT{}); | |
| return marshal; | |
| } | |
| // Will be returned in two different registers. Generate {lowTy, HiTy} for | |
| // the LLVM IR result type. | |
| CodeGenSpecifics::Marshalling marshal; | |
| mlir::Type lowType = pickLLVMArgType(loc, context, Lo, 8u); | |
| mlir::Type hiType = pickLLVMArgType(loc, context, Hi, byteOffset - 8u); | |
| marshal.emplace_back(mlir::TupleType::get(context, {lowType, hiType}), | |
| AT{}); | |
| return marshal; | |
| } | |
| /// Marshal an argument that must be passed on the stack. | |
| CodeGenSpecifics::Marshalling | |
| passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign = | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap); | |
| // The stack is always 8 byte aligned (note 14 in 3.2.3). | |
| unsigned short align = | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8)); | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{align, /*byval=*/!isResult, /*sret=*/isResult}); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // x86_64 (x86 64 bit) Windows target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetX86_64Win : public GenericTarget<TargetX86_64Win> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // i64 pack both floats in a 64-bit GPR | |
| marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { double, double } struct of 2 double, byval, align 8 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/8, /*byval=*/true}); | |
| } else if (sem == &llvm::APFloat::IEEEquad() || | |
| sem == &llvm::APFloat::x87DoubleExtended()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byval, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // i64 pack both floats in a 64-bit GPR | |
| marshal.emplace_back(mlir::IntegerType::get(eleTy.getContext(), 64), | |
| AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { double, double } struct of 2 double, sret, align 8 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/8, /*byval=*/false, /*sret=*/true}); | |
| } else if (sem == &llvm::APFloat::IEEEquad() || | |
| sem == &llvm::APFloat::x87DoubleExtended()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, sret, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/false, /*sret=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // AArch64 target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| // AArch64 procedure call standard: | |
| // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#parameter-passing | |
| struct TargetAArch64 : public GenericTarget<TargetAArch64> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble() || | |
| sem == &llvm::APFloat::IEEEquad()) { | |
| // [2 x t] array of 2 eleTy | |
| marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| integerArgumentType(mlir::Location loc, | |
| mlir::IntegerType argTy) const override { | |
| if (argTy.getWidth() < getCIntTypeWidth() && argTy.isSignless()) { | |
| AT::IntegerExtension intExt; | |
| if (argTy.getWidth() == 1) { | |
| // Zero extend for 'i1'. | |
| intExt = AT::IntegerExtension::Zero; | |
| } else { | |
| if (triple.isOSDarwin()) { | |
| // On Darwin, sign extend. The apple developer guide specifies this as | |
| // a divergence from the AArch64PCS: | |
| // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly | |
| intExt = AT::IntegerExtension::Sign; | |
| } else { | |
| // On linux, pass directly and do not extend. | |
| intExt = AT::IntegerExtension::None; | |
| } | |
| } | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false, | |
| /*sret=*/false, /*append=*/false, | |
| /*intExt=*/intExt}); | |
| return marshal; | |
| } | |
| return GenericTarget::integerArgumentType(loc, argTy); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble() || | |
| sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| // Flatten a RecordType::TypeList containing more record types or array type | |
| static std::optional<std::vector<mlir::Type>> | |
| flattenTypeList(const RecordType::TypeList &types) { | |
| std::vector<mlir::Type> flatTypes; | |
| // The flat list will be at least the same size as the non-flat list. | |
| flatTypes.reserve(types.size()); | |
| for (auto [c, type] : types) { | |
| // Flatten record type | |
| if (auto recTy = mlir::dyn_cast<RecordType>(type)) { | |
| auto subTypeList = flattenTypeList(recTy.getTypeList()); | |
| if (!subTypeList) | |
| return std::nullopt; | |
| llvm::copy(*subTypeList, std::back_inserter(flatTypes)); | |
| continue; | |
| } | |
| // Flatten array type | |
| if (auto seqTy = mlir::dyn_cast<SequenceType>(type)) { | |
| if (seqTy.hasDynamicExtents()) | |
| return std::nullopt; | |
| std::size_t n = seqTy.getConstantArraySize(); | |
| auto eleTy = seqTy.getElementType(); | |
| // Flatten array of record types | |
| if (auto recTy = mlir::dyn_cast<RecordType>(eleTy)) { | |
| auto subTypeList = flattenTypeList(recTy.getTypeList()); | |
| if (!subTypeList) | |
| return std::nullopt; | |
| for (std::size_t i = 0; i < n; ++i) | |
| llvm::copy(*subTypeList, std::back_inserter(flatTypes)); | |
| } else { | |
| std::fill_n(std::back_inserter(flatTypes), | |
| seqTy.getConstantArraySize(), eleTy); | |
| } | |
| continue; | |
| } | |
| // Other types are already flat | |
| flatTypes.push_back(type); | |
| } | |
| return flatTypes; | |
| } | |
| // Determine if the type is a Homogenous Floating-point Aggregate (HFA). An | |
| // HFA is a record type with up to 4 floating-point members of the same type. | |
| static std::optional<int> usedRegsForHFA(fir::RecordType ty) { | |
| RecordType::TypeList types = ty.getTypeList(); | |
| if (types.empty() || types.size() > 4) | |
| return std::nullopt; | |
| std::optional<std::vector<mlir::Type>> flatTypes = flattenTypeList(types); | |
| if (!flatTypes || flatTypes->size() > 4) { | |
| return std::nullopt; | |
| } | |
| if (!isa_real(flatTypes->front())) { | |
| return std::nullopt; | |
| } | |
| return llvm::all_equal(*flatTypes) ? std::optional<int>{flatTypes->size()} | |
| : std::nullopt; | |
| } | |
| struct NRegs { | |
| int n{0}; | |
| bool isSimd{false}; | |
| }; | |
| NRegs usedRegsForRecordType(mlir::Location loc, fir::RecordType type) const { | |
| if (std::optional<int> size = usedRegsForHFA(type)) | |
| return {*size, true}; | |
| auto [size, align] = fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, type, getDataLayout(), kindMap); | |
| if (size <= 16) | |
| return {static_cast<int>((size + 7) / 8), false}; | |
| // Pass on the stack, i.e. no registers used | |
| return {}; | |
| } | |
| NRegs usedRegsForType(mlir::Location loc, mlir::Type type) const { | |
| return llvm::TypeSwitch<mlir::Type, NRegs>(type) | |
| .Case([&](mlir::IntegerType intTy) { | |
| return intTy.getWidth() == 128 ? NRegs{2, false} : NRegs{1, false}; | |
| }) | |
| .Case([&](mlir::FloatType) { return NRegs{1, true}; }) | |
| .Case([&](mlir::ComplexType) { return NRegs{2, true}; }) | |
| .Case([&](fir::LogicalType) { return NRegs{1, false}; }) | |
| .Case([&](fir::CharacterType) { return NRegs{1, false}; }) | |
| .Case([&](fir::SequenceType ty) { | |
| assert(ty.getShape().size() == 1 && | |
| "invalid array dimensions in BIND(C)"); | |
| NRegs nregs = usedRegsForType(loc, ty.getEleTy()); | |
| nregs.n *= ty.getShape()[0]; | |
| return nregs; | |
| }) | |
| .Case( | |
| [&](fir::RecordType ty) { return usedRegsForRecordType(loc, ty); }) | |
| .Case([&](fir::VectorType) { | |
| TODO(loc, "passing vector argument to C by value is not supported"); | |
| return NRegs{}; | |
| }) | |
| .Default([&](auto ty) { | |
| if (fir::conformsWithPassByRef(ty)) | |
| return NRegs{1, false}; // Pointers take 1 integer register | |
| TODO(loc, "unsupported component type for BIND(C), VALUE derived " | |
| "type argument"); | |
| return NRegs{}; | |
| }); | |
| } | |
| bool hasEnoughRegisters(mlir::Location loc, fir::RecordType type, | |
| const Marshalling &previousArguments) const { | |
| int availIntRegisters = 8; | |
| int availSIMDRegisters = 8; | |
| // Check previous arguments to see how many registers are used already | |
| for (auto [type, attr] : previousArguments) { | |
| if (availIntRegisters <= 0 || availSIMDRegisters <= 0) | |
| break; | |
| if (attr.isByVal()) | |
| continue; // Previous argument passed on the stack | |
| NRegs nregs = usedRegsForType(loc, type); | |
| if (nregs.isSimd) | |
| availSIMDRegisters -= nregs.n; | |
| else | |
| availIntRegisters -= nregs.n; | |
| } | |
| NRegs nregs = usedRegsForRecordType(loc, type); | |
| if (nregs.isSimd) | |
| return nregs.n <= availSIMDRegisters; | |
| return nregs.n <= availIntRegisters; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign = | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap); | |
| // The stack is always 8 byte aligned | |
| unsigned short align = | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8)); | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{align, /*byval=*/!isResult, /*sret=*/isResult}); | |
| return marshal; | |
| } | |
| /// AAPCS64 "Parameter passing rules" B.4: "If the argument type is a | |
| /// Composite Type that is larger than 16 bytes, then the argument is copied | |
| /// to memory allocated by the caller and the argument is replaced by a | |
| /// pointer to the copy." Marking such an argument `byval` instead would let | |
| /// the target lower it as a by-value aggregate, which does not match this | |
| /// rule. | |
| CodeGenSpecifics::Marshalling passIndirectly(mlir::Location loc, | |
| mlir::Type ty) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign = | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap); | |
| // The pointee is the caller's copy, so the alignment reported for the | |
| // pointer is the natural alignment of the aggregate. | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(ty), | |
| AT{sizeAndAlign.second, /*byval=*/false, /*sret=*/false, | |
| /*append=*/false, /*intExt=*/AT::IntegerExtension::None, | |
| /*indirect=*/true}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structType(mlir::Location loc, fir::RecordType type, bool isResult) const { | |
| NRegs nregs = usedRegsForRecordType(loc, type); | |
| // If the type needs no registers it is too large to be passed in | |
| // registers. As an argument it is passed indirectly; as a result it is | |
| // returned via the caller-provided indirect result buffer (sret). | |
| if (nregs.n == 0) { | |
| if (isResult) | |
| return passOnTheStack(loc, type, /*isResult=*/true); | |
| return passIndirectly(loc, type); | |
| } | |
| CodeGenSpecifics::Marshalling marshal; | |
| mlir::Type pcsType; | |
| if (nregs.isSimd) { | |
| pcsType = type; | |
| } else { | |
| pcsType = fir::SequenceType::get( | |
| nregs.n, mlir::IntegerType::get(type.getContext(), 64)); | |
| } | |
| marshal.emplace_back(pcsType, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType ty, | |
| const Marshalling &previousArguments) const override { | |
| if (!hasEnoughRegisters(loc, ty, previousArguments)) { | |
| return passOnTheStack(loc, ty, /*isResult=*/false); | |
| } | |
| return structType(loc, ty, /*isResult=*/false); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType ty) const override { | |
| return structType(loc, ty, /*isResult=*/true); | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // PPC (AIX 32 bit) target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetPPC : public GenericTarget<TargetPPC> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 32; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // two distinct element type arguments (re, im) | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 element type | |
| marshal.emplace_back( | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // PPC64 (AIX 64 bit) target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetPPC64 : public GenericTarget<TargetPPC64> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // two distinct element type arguments (re, im) | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 element type | |
| marshal.emplace_back( | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structType(mlir::Location loc, fir::RecordType ty, bool isResult) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign{ | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)}; | |
| unsigned short align{ | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8))}; | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{align, /*byval*/ !isResult, /*sret*/ isResult}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType ty, | |
| const Marshalling &previousArguments) const override { | |
| return structType(loc, ty, false); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType ty) const override { | |
| return structType(loc, ty, true); | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // PPC64le linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetPPC64le : public GenericTarget<TargetPPC64le> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth{64}; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // two distinct element type arguments (re, im) | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 element type | |
| marshal.emplace_back( | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| return marshal; | |
| } | |
| unsigned getElemWidth(mlir::Type ty) const { | |
| unsigned width{}; | |
| llvm::TypeSwitch<mlir::Type>(ty) | |
| .Case([&](mlir::ComplexType cmplx) { | |
| auto elemType{ | |
| mlir::dyn_cast<mlir::FloatType>(cmplx.getElementType())}; | |
| width = elemType.getWidth(); | |
| }) | |
| .Case([&](mlir::FloatType real) { width = real.getWidth(); }); | |
| return width; | |
| } | |
| // Determine if all derived types components are of the same float type with | |
| // the same width. Complex(4) is considered 2 floats and complex(8) 2 doubles. | |
| bool hasSameFloatAndWidth( | |
| fir::RecordType recTy, | |
| std::pair<mlir::Type, unsigned> &firstTypeAndWidth) const { | |
| for (auto comp : recTy.getTypeList()) { | |
| mlir::Type compType{comp.second}; | |
| if (mlir::isa<fir::RecordType>(compType)) { | |
| auto rc{hasSameFloatAndWidth(mlir::cast<fir::RecordType>(compType), | |
| firstTypeAndWidth)}; | |
| if (!rc) | |
| return false; | |
| } else { | |
| mlir::Type ty; | |
| bool isFloatType{false}; | |
| if (mlir::isa<mlir::FloatType, mlir::ComplexType>(compType)) { | |
| ty = compType; | |
| isFloatType = true; | |
| } else if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(compType)) { | |
| ty = seqTy.getEleTy(); | |
| isFloatType = mlir::isa<mlir::FloatType, mlir::ComplexType>(ty); | |
| } | |
| if (!isFloatType) { | |
| return false; | |
| } | |
| auto width{getElemWidth(ty)}; | |
| if (firstTypeAndWidth.first == nullptr) { | |
| firstTypeAndWidth.first = ty; | |
| firstTypeAndWidth.second = width; | |
| } else if (width != firstTypeAndWidth.second) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign{ | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)}; | |
| unsigned short align{ | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8))}; | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{align, /*byval=*/!isResult, /*sret=*/isResult}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structType(mlir::Location loc, fir::RecordType recTy, bool isResult) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, recTy, getDataLayout(), kindMap)}; | |
| auto recordTypeSize{sizeAndAlign.first}; | |
| mlir::Type seqTy; | |
| std::pair<mlir::Type, unsigned> firstTyAndWidth{nullptr, 0}; | |
| // If there are less than or equal to 8 floats, the structure is flatten as | |
| // an array of floats. | |
| constexpr uint64_t maxNoOfFloats{8}; | |
| // i64 type | |
| mlir::Type elemTy{mlir::IntegerType::get(recTy.getContext(), defaultWidth)}; | |
| uint64_t nElem{static_cast<uint64_t>( | |
| std::ceil(static_cast<float>(recordTypeSize * 8) / defaultWidth))}; | |
| // If the derived type components contains are all floats with the same | |
| // width, the argument is passed as an array of floats. | |
| if (hasSameFloatAndWidth(recTy, firstTyAndWidth)) { | |
| uint64_t n{}; | |
| auto firstType{firstTyAndWidth.first}; | |
| // Type is either float or complex | |
| if (auto cmplx = mlir::dyn_cast<mlir::ComplexType>(firstType)) { | |
| auto fltType{mlir::dyn_cast<mlir::FloatType>(cmplx.getElementType())}; | |
| n = static_cast<uint64_t>(8 * recordTypeSize / fltType.getWidth()); | |
| if (n <= maxNoOfFloats) { | |
| nElem = n; | |
| elemTy = fltType; | |
| } | |
| } else if (mlir::isa<mlir::FloatType>(firstType)) { | |
| auto elemSizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, firstType, getDataLayout(), kindMap)}; | |
| n = static_cast<uint64_t>(recordTypeSize / elemSizeAndAlign.first); | |
| if (n <= maxNoOfFloats) { | |
| nElem = n; | |
| elemTy = firstType; | |
| } | |
| } | |
| // Neither float nor complex | |
| assert(n > 0 && "unexpected type"); | |
| } | |
| // For function returns, only flattened if there are less than 8 | |
| // floats in total. | |
| if (isResult && | |
| ((mlir::isa<mlir::FloatType>(elemTy) && nElem > maxNoOfFloats) || | |
| !mlir::isa<mlir::FloatType>(elemTy))) { | |
| return passOnTheStack(loc, recTy, isResult); | |
| } | |
| seqTy = fir::SequenceType::get(nElem, elemTy); | |
| marshal.emplace_back(seqTy, AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType recType, | |
| const Marshalling &previousArguments) const override { | |
| auto sizeAndAlign{fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, recType, getDataLayout(), kindMap)}; | |
| if (sizeAndAlign.first > 64) { | |
| return passOnTheStack(loc, recType, false); | |
| } | |
| return structType(loc, recType, false); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType recType) const override { | |
| return structType(loc, recType, true); | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // sparc (sparc 32 bit) target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetSparc : public GenericTarget<TargetSparc> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 32; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location, mlir::Type eleTy) const override { | |
| assert(fir::isa_real(eleTy)); | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy | |
| auto structTy = | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}); | |
| marshal.emplace_back(fir::ReferenceType::get(structTy), AT{}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| assert(fir::isa_real(eleTy)); | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byval | |
| auto structTy = | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}); | |
| marshal.emplace_back(fir::ReferenceType::get(structTy), | |
| AT{/*alignment=*/0, /*byval=*/true}); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // sparcv9 (sparc 64 bit) target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetSparcV9 : public GenericTarget<TargetSparcV9> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble()) { | |
| // two distinct float, double arguments | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, byval, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Use a type that will be translated into LLVM as: | |
| // { eleTy, eleTy } struct of 2 eleTy | |
| marshal.emplace_back( | |
| mlir::TupleType::get(eleTy.getContext(), mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // RISCV64 linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetRISCV64 : public GenericTarget<TargetRISCV64> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble()) { | |
| // Two distinct element type arguments (re, im) | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byVal | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{/*alignment=*/0, /*byval=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // AMDGPU linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetAMDGPU : public GenericTarget<TargetAMDGPU> { | |
| using GenericTarget::GenericTarget; | |
| // Default size (in bits) of the index type for strings. | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // Lower COMPLEX(KIND=4) as an array of two element values. | |
| marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Pass COMPLEX(KIND=8) as two separate arguments. | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle()) { | |
| // Return COMPLEX(KIND=4) as an array of two elements. | |
| marshal.emplace_back(fir::SequenceType::get({2}, eleTy), AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEdouble()) { | |
| // Return COMPLEX(KIND=8) via an aggregate with two fields. | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // NVPTX linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetNVPTX : public GenericTarget<TargetNVPTX> { | |
| using GenericTarget::GenericTarget; | |
| // Default size (in bits) of the index type for strings. | |
| static constexpr int defaultWidth = 64; | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| TODO(loc, "handle complex argument types"); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| TODO(loc, "handle complex return types"); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // LoongArch64 linux target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetLoongArch64 : public GenericTarget<TargetLoongArch64> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| static constexpr int GRLen = defaultWidth; /* eight bytes */ | |
| static constexpr int GRLenInChar = GRLen / 8; | |
| static constexpr int FRLen = defaultWidth; /* eight bytes */ | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble()) { | |
| // Two distinct element type arguments (re, im) | |
| marshal.emplace_back(eleTy, AT{}); | |
| marshal.emplace_back(eleTy, AT{}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, byval | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "argument"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location loc, mlir::Type eleTy) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| const auto *sem = &floatToSemantics(kindMap, eleTy); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { t, t } struct of 2 eleTy, byVal | |
| marshal.emplace_back(mlir::TupleType::get(eleTy.getContext(), | |
| mlir::TypeRange{eleTy, eleTy}), | |
| AT{/*alignment=*/0, /*byval=*/true}); | |
| } else if (sem == &llvm::APFloat::IEEEquad()) { | |
| // Use a type that will be translated into LLVM as: | |
| // { fp128, fp128 } struct of 2 fp128, sret, align 16 | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/16, /*byval=*/false, /*sret=*/true}); | |
| } else { | |
| typeTodo(sem, loc, "return"); | |
| } | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| integerArgumentType(mlir::Location loc, | |
| mlir::IntegerType argTy) const override { | |
| if (argTy.getWidth() == 32) { | |
| // LA64 LP64D ABI requires unsigned 32 bit integers to be sign extended. | |
| // Therefore, Flang also follows it if a function needs to be | |
| // interoperable with C. | |
| // | |
| // Currently, it only adds `signext` attribute to the dummy arguments and | |
| // return values in the function signatures, but it does not add the | |
| // corresponding attribute to the actual arguments and return values in | |
| // `fir.call` instruction. Thanks to LLVM's integration of all these | |
| // attributes, the modification is still effective. | |
| CodeGenSpecifics::Marshalling marshal; | |
| AT::IntegerExtension intExt = AT::IntegerExtension::Sign; | |
| marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false, | |
| /*sret=*/false, /*append=*/false, | |
| /*intExt=*/intExt}); | |
| return marshal; | |
| } | |
| return GenericTarget::integerArgumentType(loc, argTy); | |
| } | |
| /// Flatten non-basic types, resulting in an array of types containing only | |
| /// `IntegerType` and `FloatType`. | |
| llvm::SmallVector<mlir::Type> flattenTypeList(mlir::Location loc, | |
| const mlir::Type type) const { | |
| llvm::SmallVector<mlir::Type> flatTypes; | |
| llvm::TypeSwitch<mlir::Type>(type) | |
| .Case([&](mlir::IntegerType intTy) { | |
| if (intTy.getWidth() != 0) | |
| flatTypes.push_back(intTy); | |
| }) | |
| .Case([&](mlir::FloatType floatTy) { | |
| if (floatTy.getWidth() != 0) | |
| flatTypes.push_back(floatTy); | |
| }) | |
| .Case([&](mlir::ComplexType cmplx) { | |
| const auto *sem = &floatToSemantics(kindMap, cmplx.getElementType()); | |
| if (sem == &llvm::APFloat::IEEEsingle() || | |
| sem == &llvm::APFloat::IEEEdouble() || | |
| sem == &llvm::APFloat::IEEEquad()) | |
| std::fill_n(std::back_inserter(flatTypes), 2, | |
| cmplx.getElementType()); | |
| else | |
| TODO(loc, "unsupported complex type(not IEEEsingle, IEEEdouble, " | |
| "IEEEquad) as a structure component for BIND(C), " | |
| "VALUE derived type argument and type return"); | |
| }) | |
| .Case([&](fir::LogicalType logicalTy) { | |
| const unsigned width = | |
| kindMap.getLogicalBitsize(logicalTy.getFKind()); | |
| if (width != 0) | |
| flatTypes.push_back( | |
| mlir::IntegerType::get(type.getContext(), width)); | |
| }) | |
| .Case([&](fir::CharacterType charTy) { | |
| assert(kindMap.getCharacterBitsize(charTy.getFKind()) <= 8 && | |
| "the bit size of characterType as an interoperable type must " | |
| "not exceed 8"); | |
| for (unsigned i = 0; i < charTy.getLen(); ++i) | |
| flatTypes.push_back(mlir::IntegerType::get(type.getContext(), 8)); | |
| }) | |
| .Case([&](fir::SequenceType seqTy) { | |
| if (!seqTy.hasDynamicExtents()) { | |
| const std::uint64_t numOfEle = seqTy.getConstantArraySize(); | |
| mlir::Type eleTy = seqTy.getEleTy(); | |
| if (!mlir::isa<mlir::IntegerType, mlir::FloatType>(eleTy)) { | |
| llvm::SmallVector<mlir::Type> subTypeList = | |
| flattenTypeList(loc, eleTy); | |
| if (subTypeList.size() != 0) | |
| for (std::uint64_t i = 0; i < numOfEle; ++i) | |
| llvm::copy(subTypeList, std::back_inserter(flatTypes)); | |
| } else { | |
| std::fill_n(std::back_inserter(flatTypes), numOfEle, eleTy); | |
| } | |
| } else | |
| TODO(loc, "unsupported dynamic extent sequence type as a structure " | |
| "component for BIND(C), " | |
| "VALUE derived type argument and type return"); | |
| }) | |
| .Case([&](fir::RecordType recTy) { | |
| for (auto &component : recTy.getTypeList()) { | |
| mlir::Type eleTy = component.second; | |
| llvm::SmallVector<mlir::Type> subTypeList = | |
| flattenTypeList(loc, eleTy); | |
| if (subTypeList.size() != 0) | |
| llvm::copy(subTypeList, std::back_inserter(flatTypes)); | |
| } | |
| }) | |
| .Case([&](fir::VectorType vecTy) { | |
| auto sizeAndAlign = fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, vecTy, getDataLayout(), kindMap); | |
| if (sizeAndAlign.first == 2 * GRLenInChar) | |
| flatTypes.push_back( | |
| mlir::IntegerType::get(type.getContext(), 2 * GRLen)); | |
| else | |
| TODO(loc, "unsupported vector width(must be 128 bits)"); | |
| }) | |
| .Default([&](mlir::Type ty) { | |
| if (fir::conformsWithPassByRef(ty)) | |
| flatTypes.push_back( | |
| mlir::IntegerType::get(type.getContext(), GRLen)); | |
| else | |
| TODO(loc, "unsupported component type for BIND(C), VALUE derived " | |
| "type argument and type return"); | |
| }); | |
| return flatTypes; | |
| } | |
| /// Determine if a struct is eligible to be passed in FARs (and GARs) (i.e., | |
| /// when flattened it contains a single fp value, fp+fp, or int+fp of | |
| /// appropriate size). | |
| bool detectFARsEligibleStruct(mlir::Location loc, fir::RecordType recTy, | |
| mlir::Type &field1Ty, | |
| mlir::Type &field2Ty) const { | |
| field1Ty = field2Ty = nullptr; | |
| llvm::SmallVector<mlir::Type> flatTypes = flattenTypeList(loc, recTy); | |
| size_t flatSize = flatTypes.size(); | |
| // Cannot be eligible if the number of flattened types is equal to 0 or | |
| // greater than 2. | |
| if (flatSize == 0 || flatSize > 2) | |
| return false; | |
| bool isFirstAvaliableFloat = false; | |
| assert((mlir::isa<mlir::IntegerType, mlir::FloatType>(flatTypes[0])) && | |
| "Type must be integerType or floatType after flattening"); | |
| if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(flatTypes[0])) { | |
| const unsigned Size = floatTy.getWidth(); | |
| // Can't be eligible if larger than the FP registers. Half precision isn't | |
| // currently supported on LoongArch and the ABI hasn't been confirmed, so | |
| // default to the integer ABI in that case. | |
| if (Size > FRLen || Size < 32) | |
| return false; | |
| isFirstAvaliableFloat = true; | |
| field1Ty = floatTy; | |
| } else if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(flatTypes[0])) { | |
| if (intTy.getWidth() > GRLen) | |
| return false; | |
| field1Ty = intTy; | |
| } | |
| // flatTypes has two elements | |
| if (flatSize == 2) { | |
| assert((mlir::isa<mlir::IntegerType, mlir::FloatType>(flatTypes[1])) && | |
| "Type must be integerType or floatType after flattening"); | |
| if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(flatTypes[1])) { | |
| const unsigned Size = floatTy.getWidth(); | |
| if (Size > FRLen || Size < 32) | |
| return false; | |
| field2Ty = floatTy; | |
| return true; | |
| } else if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(flatTypes[1])) { | |
| // Can't be eligible if an integer type was already found (int+int pairs | |
| // are not eligible). | |
| if (!isFirstAvaliableFloat) | |
| return false; | |
| if (intTy.getWidth() > GRLen) | |
| return false; | |
| field2Ty = intTy; | |
| return true; | |
| } | |
| } | |
| // return isFirstAvaliableFloat if flatTypes only has one element | |
| return isFirstAvaliableFloat; | |
| } | |
| bool checkTypeHasEnoughRegs(mlir::Location loc, int &GARsLeft, int &FARsLeft, | |
| const mlir::Type type) const { | |
| if (!type) | |
| return true; | |
| llvm::TypeSwitch<mlir::Type>(type) | |
| .Case([&](mlir::IntegerType intTy) { | |
| const unsigned width = intTy.getWidth(); | |
| if (width > 128) | |
| TODO(loc, | |
| "integerType with width exceeding 128 bits is unsupported"); | |
| if (width == 0) | |
| return; | |
| if (width <= GRLen) | |
| --GARsLeft; | |
| else if (width <= 2 * GRLen) | |
| GARsLeft = GARsLeft - 2; | |
| }) | |
| .Case([&](mlir::FloatType floatTy) { | |
| const unsigned width = floatTy.getWidth(); | |
| if (width > 128) | |
| TODO(loc, "floatType with width exceeding 128 bits is unsupported"); | |
| if (width == 0) | |
| return; | |
| if (width == 32 || width == 64) | |
| --FARsLeft; | |
| else if (width <= GRLen) | |
| --GARsLeft; | |
| else if (width <= 2 * GRLen) | |
| GARsLeft = GARsLeft - 2; | |
| }) | |
| .Default([&](mlir::Type ty) { | |
| if (fir::conformsWithPassByRef(ty)) | |
| --GARsLeft; // Pointers. | |
| else | |
| TODO(loc, "unsupported component type for BIND(C), VALUE derived " | |
| "type argument and type return"); | |
| }); | |
| return GARsLeft >= 0 && FARsLeft >= 0; | |
| } | |
| bool hasEnoughRegisters(mlir::Location loc, int GARsLeft, int FARsLeft, | |
| const Marshalling &previousArguments, | |
| const mlir::Type &field1Ty, | |
| const mlir::Type &field2Ty) const { | |
| for (auto &typeAndAttr : previousArguments) { | |
| const auto &attr = std::get<Attributes>(typeAndAttr); | |
| if (attr.isByVal()) { | |
| // Previous argument passed on the stack, and its address is passed in | |
| // GAR. | |
| --GARsLeft; | |
| continue; | |
| } | |
| // Previous aggregate arguments were marshalled into simpler arguments. | |
| const auto &type = std::get<mlir::Type>(typeAndAttr); | |
| llvm::SmallVector<mlir::Type> flatTypes = flattenTypeList(loc, type); | |
| for (auto &flatTy : flatTypes) { | |
| if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, flatTy)) | |
| return false; | |
| } | |
| } | |
| if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, field1Ty)) | |
| return false; | |
| if (!checkTypeHasEnoughRegs(loc, GARsLeft, FARsLeft, field2Ty)) | |
| return false; | |
| return true; | |
| } | |
| /// LoongArch64 subroutine calling sequence ABI in: | |
| /// https://github.com/loongson/la-abi-specs/blob/release/lapcs.adoc#subroutine-calling-sequence | |
| CodeGenSpecifics::Marshalling | |
| classifyStruct(mlir::Location loc, fir::RecordType recTy, int GARsLeft, | |
| int FARsLeft, bool isResult, | |
| const Marshalling &previousArguments) const { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto [recSize, recAlign] = fir::getTypeSizeAndAlignmentOrCrash( | |
| loc, recTy, getDataLayout(), kindMap); | |
| mlir::MLIRContext *context = recTy.getContext(); | |
| if (recSize == 0) { | |
| TODO(loc, "unsupported empty struct type for BIND(C), " | |
| "VALUE derived type argument and type return"); | |
| } | |
| if (recSize > 2 * GRLenInChar) { | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(recTy), | |
| AT{recAlign, /*byval=*/!isResult, /*sret=*/isResult}); | |
| return marshal; | |
| } | |
| // Pass by FARs(and GARs) | |
| mlir::Type field1Ty = nullptr, field2Ty = nullptr; | |
| if (detectFARsEligibleStruct(loc, recTy, field1Ty, field2Ty) && | |
| hasEnoughRegisters(loc, GARsLeft, FARsLeft, previousArguments, field1Ty, | |
| field2Ty)) { | |
| if (!isResult) { | |
| if (field1Ty) | |
| marshal.emplace_back(field1Ty, AT{}); | |
| if (field2Ty) | |
| marshal.emplace_back(field2Ty, AT{}); | |
| } else { | |
| // field1Ty is always preferred over field2Ty for assignment, so there | |
| // will never be a case where field1Ty == nullptr and field2Ty != | |
| // nullptr. | |
| if (field1Ty && !field2Ty) | |
| marshal.emplace_back(field1Ty, AT{}); | |
| else if (field1Ty && field2Ty) | |
| marshal.emplace_back( | |
| mlir::TupleType::get(context, | |
| mlir::TypeRange{field1Ty, field2Ty}), | |
| AT{/*alignment=*/0, /*byval=*/true}); | |
| } | |
| return marshal; | |
| } | |
| if (recSize <= GRLenInChar) { | |
| marshal.emplace_back(mlir::IntegerType::get(context, GRLen), AT{}); | |
| return marshal; | |
| } | |
| if (recAlign == 2 * GRLenInChar) { | |
| marshal.emplace_back(mlir::IntegerType::get(context, 2 * GRLen), AT{}); | |
| return marshal; | |
| } | |
| // recSize > GRLenInChar && recSize <= 2 * GRLenInChar | |
| marshal.emplace_back( | |
| fir::SequenceType::get({2}, mlir::IntegerType::get(context, GRLen)), | |
| AT{}); | |
| return marshal; | |
| } | |
| /// Marshal a derived type passed by value like a C struct. | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType recTy, | |
| const Marshalling &previousArguments) const override { | |
| int GARsLeft = 8; | |
| int FARsLeft = FRLen ? 8 : 0; | |
| return classifyStruct(loc, recTy, GARsLeft, FARsLeft, /*isResult=*/false, | |
| previousArguments); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType recTy) const override { | |
| // The rules for return and argument types are the same. | |
| int GARsLeft = 2; | |
| int FARsLeft = FRLen ? 2 : 0; | |
| return classifyStruct(loc, recTy, GARsLeft, FARsLeft, /*isResult=*/true, | |
| {}); | |
| } | |
| }; | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // SystemZ target specifics. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct TargetSystemZ : public GenericTarget<TargetSystemZ> { | |
| using GenericTarget::GenericTarget; | |
| static constexpr int defaultWidth = 64; | |
| auto complexType(mlir::Type eleTy, bool isResult) const { | |
| assert(fir::isa_real(eleTy)); | |
| CodeGenSpecifics::Marshalling marshal; | |
| unsigned short align{std::max( | |
| static_cast<unsigned short>(getDataLayout().getTypeABIAlignment(eleTy)), | |
| static_cast<unsigned short>(8))}; | |
| marshal.emplace_back( | |
| fir::ReferenceType::get(mlir::TupleType::get( | |
| eleTy.getContext(), mlir::TypeRange{eleTy, eleTy})), | |
| AT{/*align=*/align, /*byval=*/false, /*sret=*/isResult, | |
| /*append=*/false, /*intExt=*/AT::IntegerExtension::None, | |
| /*indirect=*/!isResult}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexArgumentType(mlir::Location /*loc*/, mlir::Type eleTy) const override { | |
| return complexType(eleTy, false); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| complexReturnType(mlir::Location /*loc*/, mlir::Type eleTy) const override { | |
| return complexType(eleTy, true); | |
| } | |
| CodeGenSpecifics::Marshalling | |
| integerArgumentType(mlir::Location loc, | |
| mlir::IntegerType argTy) const override { | |
| // SystemZ ABI requires all integers < 64 bits to be sign/zero extended. | |
| // Handle widths 32 to 63 explicitly; GenericTarget handles widths < 32 | |
| // including the i1 zero-extension special case. | |
| if (argTy.getWidth() >= 32 && argTy.getWidth() < 64) { | |
| AT::IntegerExtension intExt = argTy.isUnsigned() | |
| ? AT::IntegerExtension::Zero | |
| : AT::IntegerExtension::Sign; | |
| CodeGenSpecifics::Marshalling marshal; | |
| marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false, | |
| /*sret=*/false, /*append=*/false, | |
| /*intExt=*/intExt}); | |
| return marshal; | |
| } | |
| return GenericTarget::integerArgumentType(loc, argTy); | |
| } | |
| // Check for a single floating-point member recursively. | |
| static mlir::Type getSingleFPMember(fir::RecordType recTy) { | |
| auto typeList = recTy.getTypeList(); | |
| if (typeList.size() != 1) | |
| return {}; | |
| mlir::Type fieldTy = typeList[0].second; | |
| if (auto nestedRec = mlir::dyn_cast<fir::RecordType>(fieldTy)) | |
| return getSingleFPMember(nestedRec); | |
| if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(fieldTy)) | |
| if (floatTy.getWidth() <= 64) | |
| return floatTy; | |
| return {}; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structArgumentType(mlir::Location loc, fir::RecordType ty, | |
| const Marshalling &previousArguments) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| // Single FP member <= 8 bytes passed in FPR. | |
| if (mlir::Type fpTy = getSingleFPMember(ty)) { | |
| marshal.emplace_back(fpTy, AT{}); | |
| return marshal; | |
| } | |
| // Small structs passed in GPR (1, 2, 4, 8 bytes). | |
| auto sizeAndAlign{ | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)}; | |
| uint64_t byteSize = sizeAndAlign.first; | |
| if (byteSize == 1 || byteSize == 2 || byteSize == 4 || byteSize == 8) { | |
| marshal.emplace_back( | |
| mlir::IntegerType::get(ty.getContext(), byteSize * 8), AT{}); | |
| return marshal; | |
| } | |
| // Larger structs passed via implicit reference (pointer in register/stack). | |
| unsigned short align{ | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8))}; | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{/*align=*/align, /*byval=*/false, /*sret=*/false, | |
| /*append=*/false, | |
| /*intExt=*/AT::IntegerExtension::None, | |
| /*indirect=*/true}); | |
| return marshal; | |
| } | |
| CodeGenSpecifics::Marshalling | |
| structReturnType(mlir::Location loc, fir::RecordType ty) const override { | |
| CodeGenSpecifics::Marshalling marshal; | |
| auto sizeAndAlign{ | |
| fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap)}; | |
| unsigned short align{ | |
| std::max(sizeAndAlign.second, static_cast<unsigned short>(8))}; | |
| marshal.emplace_back(fir::ReferenceType::get(ty), | |
| AT{/*align=*/align, /*byval=*/false, /*sret=*/true}); | |
| return marshal; | |
| } | |
| }; | |
| } // namespace | |
| // Instantiate the overloaded target instance based on the triple value. | |
| // TODO: Add other targets to this file as needed. | |
| std::unique_ptr<fir::CodeGenSpecifics> fir::CodeGenSpecifics::get( | |
| mlir::MLIRContext *ctx, llvm::Triple &&trp, KindMapping &&kindMap, | |
| llvm::StringRef targetCPU, mlir::LLVM::TargetFeaturesAttr targetFeatures, | |
| llvm::StringRef targetABI, const mlir::DataLayout &dl) { | |
| switch (trp.getArch()) { | |
| default: | |
| break; | |
| case llvm::Triple::ArchType::x86: | |
| if (trp.isOSWindows()) | |
| return std::make_unique<TargetI386Win>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| else | |
| return std::make_unique<TargetI386>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::x86_64: | |
| if (trp.isOSWindows()) | |
| return std::make_unique<TargetX86_64Win>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| else | |
| return std::make_unique<TargetX86_64>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::aarch64: | |
| return std::make_unique<TargetAArch64>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::ppc: | |
| return std::make_unique<TargetPPC>(ctx, std::move(trp), std::move(kindMap), | |
| targetCPU, targetFeatures, targetABI, | |
| dl); | |
| case llvm::Triple::ArchType::ppc64: | |
| return std::make_unique<TargetPPC64>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::ppc64le: | |
| return std::make_unique<TargetPPC64le>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::sparc: | |
| return std::make_unique<TargetSparc>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::sparcv9: | |
| return std::make_unique<TargetSparcV9>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::riscv64: | |
| return std::make_unique<TargetRISCV64>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::amdgpu: | |
| return std::make_unique<TargetAMDGPU>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::nvptx64: | |
| return std::make_unique<TargetNVPTX>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::loongarch64: | |
| return std::make_unique<TargetLoongArch64>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| case llvm::Triple::ArchType::systemz: | |
| return std::make_unique<TargetSystemZ>(ctx, std::move(trp), | |
| std::move(kindMap), targetCPU, | |
| targetFeatures, targetABI, dl); | |
| } | |
| TODO(mlir::UnknownLoc::get(ctx), "target not implemented"); | |
| } | |
| std::unique_ptr<fir::CodeGenSpecifics> fir::CodeGenSpecifics::get( | |
| mlir::MLIRContext *ctx, llvm::Triple &&trp, KindMapping &&kindMap, | |
| llvm::StringRef targetCPU, mlir::LLVM::TargetFeaturesAttr targetFeatures, | |
| llvm::StringRef targetABI, const mlir::DataLayout &dl, | |
| llvm::StringRef tuneCPU) { | |
| std::unique_ptr<fir::CodeGenSpecifics> CGS = | |
| fir::CodeGenSpecifics::get(ctx, std::move(trp), std::move(kindMap), | |
| targetCPU, targetFeatures, targetABI, dl); | |
| CGS->tuneCPU = tuneCPU; | |
| return CGS; | |
| } |