//===----------------------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// // // This contains code to emit Decl nodes as CIR code. // //===----------------------------------------------------------------------===// #include "CIRGenFunction.h" #include "CIRGenOpenACCHelpers.h" #include "mlir/Dialect/OpenACC/OpenACC.h" #include "clang/AST/DeclOpenACC.h" #include "llvm/Support/SaveAndRestore.h" using namespace clang; using namespace clang::CIRGen; namespace { struct OpenACCDeclareCleanup final : EHScopeStack::Cleanup { mlir::acc::DeclareEnterOp enterOp; OpenACCDeclareCleanup(mlir::acc::DeclareEnterOp enterOp) : enterOp(enterOp) {} template void createOutOp(CIRGenFunction &cgf, InTy inOp) { if constexpr (std::is_same_v) { auto outOp = OutTy::create(cgf.getBuilder(), inOp.getLoc(), inOp, inOp.getStructured(), inOp.getImplicit(), llvm::Twine(inOp.getNameAttr()), inOp.getBounds()); outOp.setDataClause(inOp.getDataClause()); outOp.setModifiers(inOp.getModifiers()); } else { auto outOp = OutTy::create(cgf.getBuilder(), inOp.getLoc(), inOp, inOp.getVarPtr(), inOp.getStructured(), inOp.getImplicit(), llvm::Twine(inOp.getNameAttr()), inOp.getBounds()); outOp.setDataClause(inOp.getDataClause()); outOp.setModifiers(inOp.getModifiers()); } } void emit(CIRGenFunction &cgf, Flags flags) override { auto exitOp = mlir::acc::DeclareExitOp::create( cgf.getBuilder(), enterOp.getLoc(), enterOp, {}); // Some data clauses need to be referenced in 'exit', AND need to have an // operation after the exit. Copy these from the enter operation. for (mlir::Value val : enterOp.getDataClauseOperands()) { if (auto copyin = val.getDefiningOp()) { switch (copyin.getDataClause()) { default: llvm_unreachable( "OpenACC local declare clause copyin unexpected data clause"); break; case mlir::acc::DataClause::acc_copy: createOutOp(cgf, copyin); break; case mlir::acc::DataClause::acc_copyin: createOutOp(cgf, copyin); break; } } else if (auto create = val.getDefiningOp()) { switch (create.getDataClause()) { default: llvm_unreachable( "OpenACC local declare clause create unexpected data clause"); break; case mlir::acc::DataClause::acc_copyout: createOutOp(cgf, create); break; case mlir::acc::DataClause::acc_create: createOutOp(cgf, create); break; } } else if (auto present = val.getDefiningOp()) { createOutOp(cgf, present); } else if (auto dev_res = val.getDefiningOp()) { createOutOp(cgf, dev_res); } else if (val.getDefiningOp()) { // Link has no exit clauses, and shouldn't be copied. continue; } else if (val.getDefiningOp()) { // DevicePtr has no exit clauses, and shouldn't be copied. continue; } else { llvm_unreachable("OpenACC local declare clause unexpected defining op"); continue; } exitOp.getDataClauseOperandsMutable().append(val); } } }; } // namespace void CIRGenModule::emitGlobalOpenACCDecl(const OpenACCConstructDecl *d) { if (const auto *rd = dyn_cast(d)) emitGlobalOpenACCRoutineDecl(rd); else emitGlobalOpenACCDeclareDecl(cast(d)); } void CIRGenFunction::emitOpenACCDeclare(const OpenACCDeclareDecl &d) { mlir::Location exprLoc = cgm.getLoc(d.getBeginLoc()); auto enterOp = mlir::acc::DeclareEnterOp::create( builder, exprLoc, mlir::acc::DeclareTokenType::get(&cgm.getMLIRContext()), {}); emitOpenACCClauses(enterOp, OpenACCDirectiveKind::Declare, d.clauses()); ehStack.pushCleanup(CleanupKind::NormalCleanup, enterOp); } // Helper function that gets the declaration referenced by the declare clause. // This is a simplified verison of the work that `getOpenACCDataOperandInfo` // does, as it only has to get forms that 'declare' does. static const Decl *getDeclareReferencedDecl(const Expr *e) { const Expr *curVarExpr = e->IgnoreParenImpCasts(); // Since we allow array sections, we have to unpack the array sections here. // We don't have to worry about other bounds, since only variable or array // name (plus array sections as an extension) are permitted. while (const auto *ase = dyn_cast(curVarExpr)) curVarExpr = ase->getBase()->IgnoreParenImpCasts(); if (const auto *dre = dyn_cast(curVarExpr)) return dre->getFoundDecl()->getCanonicalDecl(); // MemberExpr is allowed when it is implicit 'this'. return cast(curVarExpr)->getMemberDecl()->getCanonicalDecl(); } template void CIRGenModule::emitGlobalOpenACCDeclareDataOperands( const Expr *varOperand, DataClauseTy dataClause, OpenACCModifierKind modifiers, bool structured, bool implicit, bool requiresDtor) { // This is a template argument so that we don't have to include all of // mlir::acc into CIRGenModule. static_assert(std::is_same_v); mlir::Location exprLoc = getLoc(varOperand->getBeginLoc()); const Decl *refedDecl = getDeclareReferencedDecl(varOperand); StringRef varName = getMangledName(GlobalDecl{cast(refedDecl)}); // We have to emit two separate functions in this case, an acc_ctor and an // acc_dtor. These two sections are/should remain reasonably equal, however // the order of the clauses/vs-enter&exit in them makes combining these two // sections not particularly attractive, so we have a bit of repetition. { mlir::OpBuilder::InsertionGuard guardCase(builder); auto ctorOp = mlir::acc::GlobalConstructorOp::create( builder, exprLoc, (varName + "_acc_ctor").str()); getModule().push_back(ctorOp); mlir::Block *block = builder.createBlock(&ctorOp.getRegion(), ctorOp.getRegion().end(), {}, {}); builder.setInsertionPointToEnd(block); // These things are close enough to a function handling-wise we can just // create this here. CIRGenFunction cgf{*this, builder, true}; llvm::SaveAndRestore savedCGF(curCGF, &cgf); cgf.curFn = ctorOp; CIRGenFunction::SourceLocRAIIObject fnLoc{cgf, exprLoc}; // This gets the information we need, PLUS emits the bounds correctly, so we // have to do this in both enter and exit. CIRGenFunction::OpenACCDataOperandInfo inf = cgf.getOpenACCDataOperandInfo(varOperand); auto beforeOp = BeforeOpTy::create(builder, exprLoc, inf.varValue, structured, implicit, inf.name, inf.bounds); beforeOp.setDataClause(dataClause); beforeOp.setModifiers(convertOpenACCModifiers(modifiers)); mlir::acc::DeclareEnterOp::create( builder, exprLoc, mlir::acc::DeclareTokenType::get(&getMLIRContext()), beforeOp.getResult()); mlir::acc::TerminatorOp::create(builder, exprLoc); } // copyin, create, and device_resident require a destructor, link does not. In // the case of the first three, they are all a 'getdeviceptr', followed by the // declare_exit, followed by a delete op in the destructor region. if (requiresDtor) { mlir::OpBuilder::InsertionGuard guardCase(builder); auto ctorOp = mlir::acc::GlobalDestructorOp::create( builder, exprLoc, (varName + "_acc_dtor").str()); getModule().push_back(ctorOp); mlir::Block *block = builder.createBlock(&ctorOp.getRegion(), ctorOp.getRegion().end(), {}, {}); builder.setInsertionPointToEnd(block); // These things are close enough to a function handling-wise we can just // create this here. CIRGenFunction cgf{*this, builder, true}; llvm::SaveAndRestore savedCGF(curCGF, &cgf); cgf.curFn = ctorOp; CIRGenFunction::SourceLocRAIIObject fnLoc{cgf, exprLoc}; CIRGenFunction::OpenACCDataOperandInfo inf = cgf.getOpenACCDataOperandInfo(varOperand); auto getDevPtr = mlir::acc::GetDevicePtrOp::create( builder, exprLoc, inf.varValue, structured, implicit, inf.name, inf.bounds); getDevPtr.setDataClause(dataClause); getDevPtr.setModifiers(convertOpenACCModifiers(modifiers)); mlir::acc::DeclareExitOp::create(builder, exprLoc, /*token=*/mlir::Value{}, getDevPtr.getResult()); auto deleteOp = mlir::acc::DeleteOp::create( builder, exprLoc, getDevPtr, structured, implicit, inf.name, {}); deleteOp.setDataClause(dataClause); deleteOp.setModifiers(convertOpenACCModifiers(modifiers)); mlir::acc::TerminatorOp::create(builder, exprLoc); } } namespace { // This class emits all of the information for a 'declare' at a global/ns/class // scope. Each clause results in its own acc_ctor and acc_dtor for the variable. // This class creates those and emits them properly. // This behavior is unique/special enough from the emission of statement-level // clauses that it doesn't really make sense to use that clause visitor. class OpenACCGlobalDeclareClauseEmitter final : public OpenACCClauseVisitor { CIRGenModule &cgm; public: OpenACCGlobalDeclareClauseEmitter(CIRGenModule &cgm) : cgm(cgm) {} void VisitClause(const OpenACCClause &clause) { llvm_unreachable("Invalid OpenACC clause on global Declare"); } void emitClauses(ArrayRef clauses) { this->VisitClauseList(clauses); } void VisitCopyInClause(const OpenACCCopyInClause &clause) { for (const Expr *var : clause.getVarList()) cgm.emitGlobalOpenACCDeclareDataOperands( var, mlir::acc::DataClause::acc_copyin, clause.getModifierList(), /*structured=*/true, /*implicit=*/false, /*requiresDtor=*/true); } void VisitCreateClause(const OpenACCCreateClause &clause) { for (const Expr *var : clause.getVarList()) cgm.emitGlobalOpenACCDeclareDataOperands( var, mlir::acc::DataClause::acc_create, clause.getModifierList(), /*structured=*/true, /*implicit=*/false, /*requiresDtor=*/true); } void VisitDeviceResidentClause(const OpenACCDeviceResidentClause &clause) { for (const Expr *var : clause.getVarList()) cgm.emitGlobalOpenACCDeclareDataOperands< mlir::acc::DeclareDeviceResidentOp>( var, mlir::acc::DataClause::acc_declare_device_resident, {}, /*structured=*/true, /*implicit=*/false, /*requiresDtor=*/true); } void VisitLinkClause(const OpenACCLinkClause &clause) { for (const Expr *var : clause.getVarList()) cgm.emitGlobalOpenACCDeclareDataOperands( var, mlir::acc::DataClause::acc_declare_link, {}, /*structured=*/true, /*implicit=*/false, /*requiresDtor=*/false); } }; } // namespace void CIRGenModule::emitGlobalOpenACCDeclareDecl(const OpenACCDeclareDecl *d) { // Declare creates 1 'acc_ctor' and 0-1 'acc_dtor' per clause, since it needs // a unique one on a per-variable basis. We can just use a clause emitter to // do all the work. mlir::OpBuilder::InsertionGuard guardCase(builder); OpenACCGlobalDeclareClauseEmitter em{*this}; em.emitClauses(d->clauses()); } void CIRGenFunction::emitOpenACCRoutine(const OpenACCRoutineDecl &d) { // Do nothing here. The OpenACCRoutineDeclAttr handles the implicit name // cases, and the end-of-TU handling manages the named cases. This is // necessary because these references aren't necessarily emitted themselves, // but can be named anywhere. } void CIRGenModule::emitGlobalOpenACCRoutineDecl(const OpenACCRoutineDecl *d) { // Do nothing here. The OpenACCRoutineDeclAttr handles the implicit name // cases, and the end-of-TU handling manages the named cases. This is // necessary because these references aren't necessarily emitted themselves, // but can be named anywhere. } namespace { class OpenACCRoutineClauseEmitter final : public OpenACCClauseVisitor { CIRGenModule &cgm; CIRGen::CIRGenBuilderTy &builder; mlir::acc::RoutineOp routineOp; const clang::FunctionDecl *funcDecl; llvm::SmallVector lastDeviceTypeValues; public: OpenACCRoutineClauseEmitter(CIRGenModule &cgm, CIRGen::CIRGenBuilderTy &builder, mlir::acc::RoutineOp routineOp, const clang::FunctionDecl *funcDecl) : cgm(cgm), builder(builder), routineOp(routineOp), funcDecl(funcDecl) {} void emitClauses(ArrayRef clauses) { this->VisitClauseList(clauses); } void VisitClause(const OpenACCClause &clause) { llvm_unreachable("Invalid OpenACC clause on routine"); } void VisitSeqClause(const OpenACCSeqClause &clause) { routineOp.addSeq(builder.getContext(), lastDeviceTypeValues); } void VisitWorkerClause(const OpenACCWorkerClause &clause) { routineOp.addWorker(builder.getContext(), lastDeviceTypeValues); } void VisitVectorClause(const OpenACCVectorClause &clause) { routineOp.addVector(builder.getContext(), lastDeviceTypeValues); } void VisitNoHostClause(const OpenACCNoHostClause &clause) { routineOp.setNohost(/*attrValue=*/true); } void VisitGangClause(const OpenACCGangClause &clause) { // Gang has an optional 'dim' value, which is a constant int of 1, 2, or 3. // If we don't store any expressions in the clause, there are none, else we // expect there is 1, since Sema should enforce that the single 'dim' is the // only valid value. if (clause.getNumExprs() == 0) { routineOp.addGang(builder.getContext(), lastDeviceTypeValues); } else { assert(clause.getNumExprs() == 1); auto [kind, expr] = clause.getExpr(0); assert(kind == OpenACCGangKind::Dim); llvm::APSInt curValue = expr->EvaluateKnownConstInt(cgm.getASTContext()); // The value is 1, 2, or 3, but 64 bit seems right enough. curValue = curValue.sextOrTrunc(64); routineOp.addGang(builder.getContext(), lastDeviceTypeValues, curValue.getZExtValue()); } } void VisitDeviceTypeClause(const OpenACCDeviceTypeClause &clause) { lastDeviceTypeValues.clear(); for (const DeviceTypeArgument &arg : clause.getArchitectures()) lastDeviceTypeValues.push_back(decodeDeviceType(arg.getIdentifierInfo())); } void VisitBindClause(const OpenACCBindClause &clause) { if (clause.isStringArgument()) { mlir::StringAttr value = builder.getStringAttr(clause.getStringArgument()->getString()); routineOp.addBindStrName(builder.getContext(), lastDeviceTypeValues, value); } else { assert(clause.isIdentifierArgument()); std::string bindName = cgm.getOpenACCBindMangledName( clause.getIdentifierArgument(), funcDecl); routineOp.addBindIDName( builder.getContext(), lastDeviceTypeValues, mlir::SymbolRefAttr::get(builder.getContext(), bindName)); } } }; } // namespace void CIRGenModule::emitOpenACCRoutineDecl( const clang::FunctionDecl *funcDecl, cir::FuncOp func, SourceLocation pragmaLoc, ArrayRef clauses) { mlir::OpBuilder::InsertionGuard guardCase(builder); // These need to appear at the global module. builder.setInsertionPointToEnd(&getModule().getBodyRegion().front()); mlir::Location routineLoc = getLoc(pragmaLoc); std::stringstream routineNameSS; // This follows the same naming format as Flang. routineNameSS << "acc_routine_" << routineCounter++; std::string routineName = routineNameSS.str(); // There isn't a good constructor for RoutineOp that just takes a location + // name + function, so we use one that creates an otherwise RoutineOp and // count on the visitor/emitter to fill these in. auto routineOp = mlir::acc::RoutineOp::create( builder, routineLoc, routineName, mlir::SymbolRefAttr::get(builder.getContext(), func.getName()), /*implicit=*/false); // We have to add a pointer going the other direction via an acc.routine_info, // from the func to the routine. llvm::SmallVector funcRoutines; if (auto routineInfo = func.getOperation()->getAttrOfType( mlir::acc::getRoutineInfoAttrName())) funcRoutines.append(routineInfo.getAccRoutines().begin(), routineInfo.getAccRoutines().end()); funcRoutines.push_back( mlir::SymbolRefAttr::get(builder.getContext(), routineName)); func.getOperation()->setAttr( mlir::acc::getRoutineInfoAttrName(), mlir::acc::RoutineInfoAttr::get(func.getContext(), funcRoutines)); OpenACCRoutineClauseEmitter emitter{*this, builder, routineOp, funcDecl}; emitter.emitClauses(clauses); }