diff --git a/flang/include/flang/Optimizer/CodeGen/CGPasses.td b/flang/include/flang/Optimizer/CodeGen/CGPasses.td --- a/flang/include/flang/Optimizer/CodeGen/CGPasses.td +++ b/flang/include/flang/Optimizer/CodeGen/CGPasses.td @@ -41,4 +41,21 @@ ]; } +def TargetRewrite : Pass<"target-rewrite", "mlir::ModuleOp"> { + let summary = "Rewrite some FIR dialect into target specific forms."; + let description = [{ + Certain abstractions in the FIR dialect need to be rewritten to reflect + representations that may differ based on the target machine. + }]; + let constructor = "::fir::createFirTargetRewritePass()"; + let dependentDialects = [ "fir::FIROpsDialect" ]; + let options = [ + Option<"forcedTargetTriple", "target", "std::string", /*default=*/"", + "Override module's target triple.">, + Option<"noCharacterConversion", "no-character-conversion", + "bool", /*default=*/"false", + "Disable target-specific conversion of CHARACTER."> + ]; +} + #endif // FORTRAN_OPTIMIZER_CODEGEN_FIR_PASSES diff --git a/flang/include/flang/Optimizer/CodeGen/CodeGen.h b/flang/include/flang/Optimizer/CodeGen/CodeGen.h --- a/flang/include/flang/Optimizer/CodeGen/CodeGen.h +++ b/flang/include/flang/Optimizer/CodeGen/CodeGen.h @@ -22,6 +22,16 @@ /// the code gen (to LLVM-IR dialect) conversion. std::unique_ptr createFirCodeGenRewritePass(); +// FirTargetRewritePass options. +struct TargetRewriteOptions { + bool noCharacterConversion{}; +}; + +/// Prerequiste pass for code gen. Perform intermediate rewrites to tailor the +/// FIR for the chosen target. +std::unique_ptr> createFirTargetRewritePass( + const TargetRewriteOptions &options = TargetRewriteOptions()); + /// Convert FIR to the LLVM IR dialect std::unique_ptr createFIRToLLVMPass(); diff --git a/flang/lib/Optimizer/CodeGen/CMakeLists.txt b/flang/lib/Optimizer/CodeGen/CMakeLists.txt --- a/flang/lib/Optimizer/CodeGen/CMakeLists.txt +++ b/flang/lib/Optimizer/CodeGen/CMakeLists.txt @@ -2,6 +2,8 @@ CGOps.cpp CodeGen.cpp PreCGRewrite.cpp + Target.cpp + TargetRewrite.cpp DEPENDS FIRDialect diff --git a/flang/lib/Optimizer/CodeGen/Target.h b/flang/lib/Optimizer/CodeGen/Target.h new file mode 100644 --- /dev/null +++ b/flang/lib/Optimizer/CodeGen/Target.h @@ -0,0 +1,79 @@ +//===- Target.h - target specific details -----------------------*- C++ -*-===// +// +// 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/ +// +//===----------------------------------------------------------------------===// + +#ifndef FORTRAN_OPTMIZER_CODEGEN_TARGET_H +#define FORTRAN_OPTMIZER_CODEGEN_TARGET_H + +#include "flang/Optimizer/Support/KindMapping.h" +#include "mlir/IR/BuiltinTypes.h" +#include "llvm/ADT/Triple.h" +#include +#include +#include + +namespace fir { + +namespace details { +/// Extra information about how to marshal an argument or return value that +/// modifies a signature per a particular ABI's calling convention. +/// Note: llvm::Attribute is not used directly, because its use depends on an +/// LLVMContext. +class Attributes { +public: + Attributes(bool append = false) : append{append} {} + + bool isAppend() const { return append; } + +private: + bool append : 1; +}; + +} // namespace details + +/// Some details of how to represent certain features depend on the target and +/// ABI that is being used. These specifics are captured here and guide the +/// lowering of FIR to LLVM-IR dialect. +class CodeGenSpecifics { +public: + using Attributes = details::Attributes; + using Marshalling = std::vector>; + + static std::unique_ptr + get(mlir::MLIRContext *ctx, llvm::Triple &&trp, KindMapping &&kindMap); + + CodeGenSpecifics(mlir::MLIRContext *ctx, llvm::Triple &&trp, + KindMapping &&kindMap) + : context{*ctx}, triple{std::move(trp)}, kindMap{std::move(kindMap)} {} + CodeGenSpecifics() = delete; + virtual ~CodeGenSpecifics() {} + + /// Type representation of a `boxchar` type argument when passed by value. + /// An argument value may need to be passed as a (safe) reference argument. + /// + /// A function that returns a `boxchar` type value must already have + /// converted that return value to a parameter decorated with the 'sret' + /// Attribute (https://llvm.org/docs/LangRef.html#parameter-attributes). + /// This requirement is in keeping with Fortran semantics, which require the + /// caller to allocate the space for the return CHARACTER value and pass + /// a pointer and the length of that space (a boxchar) to the called function. + virtual Marshalling boxcharArgumentType(mlir::Type eleTy, + bool sret = false) const = 0; + +protected: + mlir::MLIRContext &context; + llvm::Triple triple; + KindMapping kindMap; +}; + +} // namespace fir + +#endif // FORTRAN_OPTMIZER_CODEGEN_TARGET_H diff --git a/flang/lib/Optimizer/CodeGen/Target.cpp b/flang/lib/Optimizer/CodeGen/Target.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Optimizer/CodeGen/Target.cpp @@ -0,0 +1,143 @@ +//===-- 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 "Target.h" +#include "flang/Optimizer/Dialect/FIRType.h" +#include "flang/Optimizer/Support/KindMapping.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/TypeRange.h" + +#define DEBUG_TYPE "flang-codegen-target" + +using namespace fir; + +namespace { +template +struct GenericTarget : public CodeGenSpecifics { + using CodeGenSpecifics::CodeGenSpecifics; + using AT = CodeGenSpecifics::Attributes; + + Marshalling boxcharArgumentType(mlir::Type eleTy, bool sret) 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{}); + // Return value arguments are grouped as a pair. Others 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{/*append=*/!sret}); + return marshal; + } +}; +} // namespace + +//===----------------------------------------------------------------------===// +// i386 (x86 32 bit) linux target specifics. +//===----------------------------------------------------------------------===// + +namespace { +struct TargetI386 : public GenericTarget { + using GenericTarget::GenericTarget; + + static constexpr int defaultWidth = 32; +}; +} // namespace + +//===----------------------------------------------------------------------===// +// x86_64 (x86 64 bit) linux target specifics. +//===----------------------------------------------------------------------===// + +namespace { +struct TargetX86_64 : public GenericTarget { + using GenericTarget::GenericTarget; + + static constexpr int defaultWidth = 64; +}; +} // namespace + +//===----------------------------------------------------------------------===// +// AArch64 linux target specifics. +//===----------------------------------------------------------------------===// + +namespace { +struct TargetAArch64 : public GenericTarget { + using GenericTarget::GenericTarget; + + static constexpr int defaultWidth = 64; +}; +} // namespace + +//===----------------------------------------------------------------------===// +// PPC64le linux target specifics. +//===----------------------------------------------------------------------===// + +namespace { +struct TargetPPC64le : public GenericTarget { + using GenericTarget::GenericTarget; + + static constexpr int defaultWidth = 64; +}; +} // namespace + +// Instantiate the overloaded target instance based on the triple value. +// Currently, the implementation only instantiates `i386-unknown-linux-gnu`, +// `x86_64-unknown-linux-gnu`, aarch64 and ppc64le like triples. Other targets +// should be added to this file as needed. +std::unique_ptr +fir::CodeGenSpecifics::get(mlir::MLIRContext *ctx, llvm::Triple &&trp, + KindMapping &&kindMap) { + switch (trp.getArch()) { + default: + break; + case llvm::Triple::ArchType::x86: + switch (trp.getOS()) { + default: + break; + case llvm::Triple::OSType::Linux: + case llvm::Triple::OSType::Darwin: + return std::make_unique(ctx, std::move(trp), + std::move(kindMap)); + } + break; + case llvm::Triple::ArchType::x86_64: + switch (trp.getOS()) { + default: + break; + case llvm::Triple::OSType::Linux: + case llvm::Triple::OSType::Darwin: + return std::make_unique(ctx, std::move(trp), + std::move(kindMap)); + } + break; + case llvm::Triple::ArchType::aarch64: + switch (trp.getOS()) { + default: + break; + case llvm::Triple::OSType::Linux: + case llvm::Triple::OSType::Darwin: + return std::make_unique(ctx, std::move(trp), + std::move(kindMap)); + } + break; + case llvm::Triple::ArchType::ppc64le: + switch (trp.getOS()) { + default: + break; + case llvm::Triple::OSType::Linux: + return std::make_unique(ctx, std::move(trp), + std::move(kindMap)); + } + break; + } + llvm::report_fatal_error("target not implemented"); +} diff --git a/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Optimizer/CodeGen/TargetRewrite.cpp @@ -0,0 +1,367 @@ +//===-- TargetRewrite.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 +// +//===----------------------------------------------------------------------===// +// +// Target rewrite: rewriting of ops to make target-specific lowerings manifest. +// LLVM expects different lowering idioms to be used for distinct target +// triples. These distinctions are handled by this pass. +// +// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ +// +//===----------------------------------------------------------------------===// + +#include "PassDetail.h" +#include "Target.h" +#include "flang/Lower/Todo.h" +#include "flang/Optimizer/CodeGen/CodeGen.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" +#include "flang/Optimizer/Dialect/FIROps.h" +#include "flang/Optimizer/Dialect/FIRType.h" +#include "flang/Optimizer/Support/FIRContext.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/Debug.h" + +using namespace fir; + +#define DEBUG_TYPE "flang-target-rewrite" + +namespace { + +/// Fixups for updating a FuncOp's arguments and return values. +struct FixupTy { + enum class Codes { CharPair, Trailing }; + + FixupTy(Codes code, std::size_t index, std::size_t second = 0) + : code{code}, index{index}, second{second} {} + FixupTy(Codes code, std::size_t index, + std::function &&finalizer) + : code{code}, index{index}, finalizer{finalizer} {} + FixupTy(Codes code, std::size_t index, std::size_t second, + std::function &&finalizer) + : code{code}, index{index}, second{second}, finalizer{finalizer} {} + + Codes code; + std::size_t index; + std::size_t second{}; + llvm::Optional> finalizer{}; +}; // namespace + +/// Target-specific rewriting of the FIR. This is a prerequisite pass to code +/// generation that traverses the FIR and modifies types and operations to a +/// form that is appropriate for the specific target. LLVM IR has specific +/// idioms that are used for distinct target processor and ABI combinations. +class TargetRewrite : public TargetRewriteBase { +public: + TargetRewrite(const TargetRewriteOptions &options) { + noCharacterConversion = options.noCharacterConversion; + } + + void runOnOperation() override final { + auto &context = getContext(); + mlir::OpBuilder rewriter(&context); + + auto mod = getModule(); + if (!forcedTargetTriple.empty()) { + setTargetTriple(mod, forcedTargetTriple); + } + + auto specifics = CodeGenSpecifics::get(getOperation().getContext(), + getTargetTriple(getOperation()), + getKindMapping(getOperation())); + setMembers(specifics.get(), &rewriter); + + // Perform type conversion on signatures and call sites. + if (mlir::failed(convertTypes(mod))) { + mlir::emitError(mlir::UnknownLoc::get(&context), + "error in converting types to target abi"); + signalPassFailure(); + } + + // Convert ops in target-specific patterns. + mod.walk([&](mlir::Operation *op) { + if (auto call = dyn_cast(op)) { + if (!hasPortableSignature(call.getFunctionType())) + convertCallOp(call); + } else if (auto dispatch = dyn_cast(op)) { + if (!hasPortableSignature(dispatch.getFunctionType())) + convertCallOp(dispatch); + } + }); + + clearMembers(); + } + + mlir::ModuleOp getModule() { return getOperation(); } + + // Convert fir.call and fir.dispatch Ops. + template + void convertCallOp(A callOp) { + auto fnTy = callOp.getFunctionType(); + auto loc = callOp.getLoc(); + rewriter->setInsertionPoint(callOp); + llvm::SmallVector newResTys; + llvm::SmallVector newInTys; + llvm::SmallVector newOpers; + + // If the call is indirect, the first argument must still be the function + // to call. + int dropFront = 0; + if constexpr (std::is_same_v, fir::CallOp>) { + if (!callOp.callee().hasValue()) { + newInTys.push_back(fnTy.getInput(0)); + newOpers.push_back(callOp.getOperand(0)); + dropFront = 1; + } + } + + // Determine the rewrite function, `wrap`, for the result value. + llvm::Optional> wrap; + if (fnTy.getResults().size() == 1) { + mlir::Type ty = fnTy.getResult(0); + newResTys.push_back(ty); + } else if (fnTy.getResults().size() > 1) { + TODO(loc, "multiple results not supported yet"); + } + + llvm::SmallVector trailingInTys; + llvm::SmallVector trailingOpers; + for (auto e : llvm::enumerate( + llvm::zip(fnTy.getInputs().drop_front(dropFront), + callOp.getOperands().drop_front(dropFront)))) { + mlir::Type ty = std::get<0>(e.value()); + mlir::Value oper = std::get<1>(e.value()); + unsigned index = e.index(); + llvm::TypeSwitch(ty) + .template Case([&](BoxCharType boxTy) { + bool sret; + if constexpr (std::is_same_v, fir::CallOp>) { + sret = callOp.callee() && + functionArgIsSRet(index, + getModule().lookupSymbol( + *callOp.callee())); + } else { + // TODO: dispatch case; how do we put arguments on a call? + // We cannot put both an sret and the dispatch object first. + sret = false; + TODO(loc, "dispatch + sret not supported yet"); + } + auto m = specifics->boxcharArgumentType(boxTy.getEleTy(), sret); + auto unbox = + rewriter->create(loc, std::get(m[0]), + std::get(m[1]), oper); + // unboxed CHARACTER arguments + for (auto e : llvm::enumerate(m)) { + unsigned idx = e.index(); + auto attr = std::get(e.value()); + auto argTy = std::get(e.value()); + if (attr.isAppend()) { + trailingInTys.push_back(argTy); + trailingOpers.push_back(unbox.getResult(idx)); + } else { + newInTys.push_back(argTy); + newOpers.push_back(unbox.getResult(idx)); + } + } + }) + .Default([&](mlir::Type ty) { + newInTys.push_back(ty); + newOpers.push_back(oper); + }); + } + newInTys.insert(newInTys.end(), trailingInTys.begin(), trailingInTys.end()); + newOpers.insert(newOpers.end(), trailingOpers.begin(), trailingOpers.end()); + if constexpr (std::is_same_v, fir::CallOp>) { + fir::CallOp newCall; + if (callOp.callee().hasValue()) { + newCall = rewriter->create(loc, callOp.callee().getValue(), + newResTys, newOpers); + } else { + // Force new type on the input operand. + newOpers[0].setType(mlir::FunctionType::get( + callOp.getContext(), + mlir::TypeRange{newInTys}.drop_front(dropFront), newResTys)); + newCall = rewriter->create(loc, newResTys, newOpers); + } + LLVM_DEBUG(llvm::dbgs() << "replacing call with " << newCall << '\n'); + if (wrap.hasValue()) + replaceOp(callOp, (*wrap)(newCall.getOperation())); + else + replaceOp(callOp, newCall.getResults()); + } else { + // A is fir::DispatchOp + TODO(loc, "dispatch not implemented"); + } + } + /// Convert the type signatures on all the functions present in the module. + /// As the type signature is being changed, this must also update the + /// function itself to use any new arguments, etc. + mlir::LogicalResult convertTypes(mlir::ModuleOp mod) { + for (auto fn : mod.getOps()) + convertSignature(fn); + return mlir::success(); + } + + /// If the signature does not need any special target-specific converions, + /// 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) { + assert(signature.isa()); + auto func = signature.dyn_cast(); + for (auto ty : func.getResults()) + if ((ty.isa() && !noCharacterConversion)) { + LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n"); + return false; + } + for (auto ty : func.getInputs()) + if ((ty.isa() && !noCharacterConversion)) { + LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n"); + return false; + } + return true; + } + + /// Rewrite the signatures and body of the `FuncOp`s in the module for + /// the immediately subsequent target code gen. + void convertSignature(mlir::FuncOp func) { + auto funcTy = func.getType().cast(); + if (hasPortableSignature(funcTy)) + return; + llvm::SmallVector newResTys; + llvm::SmallVector newInTys; + llvm::SmallVector fixups; + + // Convert return value(s) + for (auto ty : funcTy.getResults()) + newResTys.push_back(ty); + + // Convert arguments + llvm::SmallVector trailingTys; + for (auto e : llvm::enumerate(funcTy.getInputs())) { + auto ty = e.value(); + unsigned index = e.index(); + llvm::TypeSwitch(ty) + .Case([&](BoxCharType boxTy) { + if (noCharacterConversion) { + newInTys.push_back(boxTy); + } else { + // Convert a CHARACTER argument type. This can involve separating + // the pointer and the LEN into two arguments and moving the LEN + // argument to the end of the arg list. + bool sret = functionArgIsSRet(index, func); + for (auto e : llvm::enumerate(specifics->boxcharArgumentType( + boxTy.getEleTy(), sret))) { + auto &tup = e.value(); + auto index = e.index(); + auto attr = std::get(tup); + auto argTy = std::get(tup); + if (attr.isAppend()) { + trailingTys.push_back(argTy); + } else { + if (sret) { + fixups.emplace_back(FixupTy::Codes::CharPair, + newInTys.size(), index); + } else { + fixups.emplace_back(FixupTy::Codes::Trailing, + newInTys.size(), trailingTys.size()); + } + newInTys.push_back(argTy); + } + } + } + }) + .Default([&](mlir::Type ty) { newInTys.push_back(ty); }); + } + + if (!func.empty()) { + // If the function has a body, then apply the fixups to the arguments and + // return ops as required. These fixups are done in place. + auto loc = func.getLoc(); + const auto fixupSize = fixups.size(); + const auto oldArgTys = func.getType().getInputs(); + int offset = 0; + for (std::remove_const_t i = 0; i < fixupSize; ++i) { + const auto &fixup = fixups[i]; + switch (fixup.code) { + case FixupTy::Codes::CharPair: { + // The FIR boxchar argument has been split into a pair of distinct + // arguments that are in juxtaposition to each other. + auto newArg = + func.front().insertArgument(fixup.index, newInTys[fixup.index]); + if (fixup.second == 1) { + rewriter->setInsertionPointToStart(&func.front()); + auto boxTy = oldArgTys[fixup.index - offset - fixup.second]; + auto box = rewriter->create( + loc, boxTy, func.front().getArgument(fixup.index - 1), newArg); + func.getArgument(fixup.index + 1).replaceAllUsesWith(box); + func.front().eraseArgument(fixup.index + 1); + offset++; + } + } break; + case FixupTy::Codes::Trailing: { + // The FIR argument has been split into a pair of distinct arguments. + // The first part of the pair appears in the original argument + // position. The second part of the pair is appended after all the + // original arguments. (Boxchar arguments.) + auto newBufArg = + func.front().insertArgument(fixup.index, newInTys[fixup.index]); + auto newLenArg = func.front().addArgument(trailingTys[fixup.second]); + auto boxTy = oldArgTys[fixup.index - offset]; + rewriter->setInsertionPointToStart(&func.front()); + auto box = + rewriter->create(loc, boxTy, newBufArg, newLenArg); + func.getArgument(fixup.index + 1).replaceAllUsesWith(box); + func.front().eraseArgument(fixup.index + 1); + } break; + } + } + } + + // Set the new type and finalize the arguments, etc. + newInTys.insert(newInTys.end(), trailingTys.begin(), trailingTys.end()); + auto newFuncTy = + mlir::FunctionType::get(func.getContext(), newInTys, newResTys); + LLVM_DEBUG(llvm::dbgs() << "new func: " << newFuncTy << '\n'); + func.setType(newFuncTy); + + for (auto &fixup : fixups) + if (fixup.finalizer) + (*fixup.finalizer)(func); + } + + inline bool functionArgIsSRet(unsigned index, mlir::FuncOp func) { + if (auto attr = func.getArgAttrOfType(index, "llvm.sret")) + return true; + return false; + } + +private: + // Replace `op` and remove it. + void replaceOp(mlir::Operation *op, mlir::ValueRange newValues) { + op->replaceAllUsesWith(newValues); + op->dropAllReferences(); + op->erase(); + } + + inline void setMembers(CodeGenSpecifics *s, mlir::OpBuilder *r) { + specifics = s; + rewriter = r; + } + + inline void clearMembers() { setMembers(nullptr, nullptr); } + + CodeGenSpecifics *specifics{}; + mlir::OpBuilder *rewriter; +}; // namespace +} // namespace + +std::unique_ptr> +fir::createFirTargetRewritePass(const TargetRewriteOptions &options) { + return std::make_unique(options); +} diff --git a/flang/test/Fir/target-rewrite-triple.fir b/flang/test/Fir/target-rewrite-triple.fir new file mode 100644 --- /dev/null +++ b/flang/test/Fir/target-rewrite-triple.fir @@ -0,0 +1,12 @@ +// RUN: fir-opt --target-rewrite %s | FileCheck %s --check-prefix=UNCHANGED +// RUN: fir-opt --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefix=CHANGED + +// UNCHANGED: fir.triple = "aarch64-unknown-linux-gnu" +// CHANGED: fir.triple = "x86_64-unknown-linux-gnu" +// CHANGED-NOT: fir.triple = "aarch64-unknown-linux-gnu" +module attributes {fir.triple = "aarch64-unknown-linux-gnu"} { + func @dummyfunc() -> () { + return + } +} + diff --git a/flang/test/Fir/target.fir b/flang/test/Fir/target.fir new file mode 100644 --- /dev/null +++ b/flang/test/Fir/target.fir @@ -0,0 +1,95 @@ +// RUN: fir-opt --target-rewrite="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefix=INT32 +// RUN: fir-opt --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefix=INT64 +// RUN: fir-opt --target-rewrite="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefix=INT64 +// RUN: fir-opt --target-rewrite="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefix=INT64 + +// Test that we rewrite the signatures and bodies of functions that take boxchar +// parameters. +// INT32-LABEL: @boxcharparams +// INT32-SAME: ([[ARG0:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG1:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG2:%[0-9A-Za-z]+]]: i32, [[ARG3:%[0-9A-Za-z]+]]: i32) -> i64 +// INT64-LABEL: @boxcharparams +// INT64-SAME: ([[ARG0:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG1:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG2:%[0-9A-Za-z]+]]: i64, [[ARG3:%[0-9A-Za-z]+]]: i64) -> i64 +func @boxcharparams(%arg0 : !fir.boxchar<1>, %arg1 : !fir.boxchar<1>) -> i64 { + // INT32-DAG: [[B0:%[0-9]+]] = fir.emboxchar [[ARG1]], [[ARG3]] : (!fir.ref>, i32) -> !fir.boxchar<1> + // INT32-DAG: [[B1:%[0-9]+]] = fir.emboxchar [[ARG0]], [[ARG2]] : (!fir.ref>, i32) -> !fir.boxchar<1> + // INT32-DAG: fir.unboxchar [[B0]] : (!fir.boxchar<1>) -> (!fir.ref>, i64) + // INT32-DAG: fir.unboxchar [[B1]] : (!fir.boxchar<1>) -> (!fir.ref>, i64) + // INT64-DAG: [[B0:%[0-9]+]] = fir.emboxchar [[ARG1]], [[ARG3]] : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT64-DAG: [[B1:%[0-9]+]] = fir.emboxchar [[ARG0]], [[ARG2]] : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT64-DAG: fir.unboxchar [[B0]] : (!fir.boxchar<1>) -> (!fir.ref>, i64) + // INT64-DAG: fir.unboxchar [[B1]] : (!fir.boxchar<1>) -> (!fir.ref>, i64) + %1:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref>, i64) + %2:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref>, i64) + %3 = arith.addi %1#1, %2#1 : i64 + return %3 : i64 +} + +// Test that we rewrite the signatures and bodies of functions that return a +// boxchar. +// INT32-LABEL: @boxcharsret +// INT32-SAME: ([[ARG0:%[0-9A-Za-z]+]]: !fir.ref> {llvm.sret}, [[ARG1:%[0-9A-Za-z]+]]: i32, [[ARG2:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG3:%[0-9A-Za-z]+]]: i32) +// INT64-LABEL: @boxcharsret +// INT64-SAME: ([[ARG0:%[0-9A-Za-z]+]]: !fir.ref> {llvm.sret}, [[ARG1:%[0-9A-Za-z]+]]: i64, [[ARG2:%[0-9A-Za-z]+]]: !fir.ref>, [[ARG3:%[0-9A-Za-z]+]]: i64) +func @boxcharsret(%arg0 : !fir.boxchar<1> {llvm.sret}, %arg1 : !fir.boxchar<1>) { + // INT32-DAG: [[B0:%[0-9]+]] = fir.emboxchar [[ARG0]], [[ARG1]] : (!fir.ref>, i32) -> !fir.boxchar<1> + // INT32-DAG: [[B1:%[0-9]+]] = fir.emboxchar [[ARG2]], [[ARG3]] : (!fir.ref>, i32) -> !fir.boxchar<1> + // INT32-DAG: fir.unboxchar [[B0]] : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + // INT32-DAG: fir.unboxchar [[B1]] : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + // INT64-DAG: [[B0:%[0-9]+]] = fir.emboxchar [[ARG0]], [[ARG1]] : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT64-DAG: [[B1:%[0-9]+]] = fir.emboxchar [[ARG2]], [[ARG3]] : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT64-DAG: fir.unboxchar [[B0]] : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + // INT64-DAG: fir.unboxchar [[B1]] : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + %1:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + %2:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref>>, i64) + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %3 = fir.convert %1#1 : (i64) -> index + %last = arith.subi %3, %c1 : index + fir.do_loop %i = %c0 to %last step %c1 { + %in_pos = fir.coordinate_of %2#0, %i : (!fir.ref>>, index) -> !fir.ref> + %out_pos = fir.coordinate_of %1#0, %i : (!fir.ref>>, index) -> !fir.ref> + %ch = fir.load %in_pos : !fir.ref> + fir.store %ch to %out_pos : !fir.ref> + } + return +} + +// Test that we rewrite the signatures of functions with a sret parameter and +// several other parameters. +// INT32-LABEL: @boxcharmultiple +// INT32-SAME: ({{%[0-9A-Za-z]+}}: !fir.ref> {llvm.sret}, {{%[0-9A-Za-z]+}}: i32, {{%[0-9A-Za-z]+}}: !fir.ref>, {{%[0-9A-Za-z]+}}: !fir.ref>, {{%[0-9A-Za-z]+}}: i32, {{%[0-9A-Za-z]+}}: i32) +// INT64-LABEL: @boxcharmultiple +// INT64-SAME: ({{%[0-9A-Za-z]+}}: !fir.ref> {llvm.sret}, {{%[0-9A-Za-z]+}}: i64, {{%[0-9A-Za-z]+}}: !fir.ref>, {{%[0-9A-Za-z]+}}: !fir.ref>, {{%[0-9A-Za-z]+}}: i64, {{%[0-9A-Za-z]+}}: i64) +func @boxcharmultiple(%arg0 : !fir.boxchar<1> {llvm.sret}, %arg1 : !fir.boxchar<1>, %arg2 : !fir.boxchar<1>) { + return +} + +// Test that we rewrite calls to functions that take boxchar arguments. +// INT32-LABEL: @boxcharcallee(!fir.ref>, i32) +// INT64-LABEL: @boxcharcallee(!fir.ref>, i64) +func private @boxcharcallee(%x : !fir.boxchar<1>) + +// INT32: @boxchararg +// INT64: @boxchararg +func @boxchararg() { + %1 = fir.address_of (@name) : !fir.ref> + %2 = arith.constant 9 : i64 + %3 = fir.convert %1 : (!fir.ref>) -> !fir.ref> + // INT32: [[B:%[0-9A-Za-z]+]] = fir.emboxchar {{.*}} : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT32: [[U:%[0-9A-Za-z]+]]:2 = fir.unboxchar [[B]] : (!fir.boxchar<1>) -> + // (!fir.ref>, i32) + // INT64: [[B:%[0-9A-Za-z]+]] = fir.emboxchar {{.*}} : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT64: [[U:%[0-9A-Za-z]+]]:2 = fir.unboxchar [[B]] : (!fir.boxchar<1>) -> + // (!fir.ref>, i64) + %4 = fir.emboxchar %3, %2 : (!fir.ref>, i64) -> !fir.boxchar<1> + // INT32: fir.call @boxcharcallee([[U]]#0, [[U]]#1) : (!fir.ref>, i32) -> () + // INT64: fir.call @boxcharcallee([[U]]#0, [[U]]#1) : (!fir.ref>, i64) -> () + fir.call @boxcharcallee(%4) : (!fir.boxchar<1>) -> () + return +} + +fir.global @name constant : !fir.char<1,9> { + %str = fir.string_lit "Your name"(9) : !fir.char<1,9> + //constant 1 + fir.has_value %str : !fir.char<1,9> +}