//===--- CIRGenOpenMPRuntime.cpp - OpenMP code generation helpers ------=--===// // // 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 // //===----------------------------------------------------------------------===// // // Helpers for OpenMP-specific CIR code generation. // ////===--------------------------------------------------------------------===// #include "CIRGenOpenMPRuntime.h" #include "CIRGenModule.h" #include "mlir/Dialect/OpenMP/OpenMPDialect.h" #include "mlir/Dialect/OpenMP/OpenMPInterfaces.h" #include "clang/AST/OpenMPClause.h" using namespace clang; using namespace clang::CIRGen; static mlir::omp::DeclareTargetDeviceType convertDeviceType(OMPDeclareTargetDeclAttr::DevTypeTy devTy) { switch (devTy) { case OMPDeclareTargetDeclAttr::DT_Host: return mlir::omp::DeclareTargetDeviceType::host; case OMPDeclareTargetDeclAttr::DT_NoHost: return mlir::omp::DeclareTargetDeviceType::nohost; case OMPDeclareTargetDeclAttr::DT_Any: return mlir::omp::DeclareTargetDeviceType::any; } llvm_unreachable("unexpected device type"); } static mlir::omp::DeclareTargetCaptureClause convertCaptureClause(OMPDeclareTargetDeclAttr::MapTypeTy mapTy) { switch (mapTy) { case OMPDeclareTargetDeclAttr::MT_To: return mlir::omp::DeclareTargetCaptureClause::to; case OMPDeclareTargetDeclAttr::MT_Enter: return mlir::omp::DeclareTargetCaptureClause::enter; case OMPDeclareTargetDeclAttr::MT_Link: return mlir::omp::DeclareTargetCaptureClause::link; case OMPDeclareTargetDeclAttr::MT_Local: return mlir::omp::DeclareTargetCaptureClause::none; } llvm_unreachable("unexpected map type"); } /// Returns true if the declaration should be skipped based on its /// device_type attribute and the current compilation mode. static bool isAssumedToBeNotEmitted(const ValueDecl *vd, bool isDevice) { std::optional devTy = OMPDeclareTargetDeclAttr::getDeviceType(vd); if (!devTy) return false; // Do not emit device_type(nohost) functions for the host. if (!isDevice && *devTy == OMPDeclareTargetDeclAttr::DT_NoHost) return true; // Do not emit device_type(host) functions for the device. if (isDevice && *devTy == OMPDeclareTargetDeclAttr::DT_Host) return true; return false; } /// Recursively check whether the statement tree contains any OpenMP target /// execution directive (e.g. 'omp target', 'omp target parallel', etc.). /// Used to identify host functions that must be emitted on the device because /// they contain target regions that will be outlined during MLIR lowering. static bool containsTargetRegion(const Stmt *s) { if (!s) return false; if (const auto *e = dyn_cast(s)) if (isOpenMPTargetExecutionDirective(e->getDirectiveKind())) return true; for (const Stmt *child : s->children()) if (containsTargetRegion(child)) return true; return false; } bool CIRGenOpenMPRuntime::emitTargetFunctions(GlobalDecl gd) { bool isDevice = cgm.getLangOpts().OpenMPIsTargetDevice; if (!isDevice) { if (const auto *fd = dyn_cast(gd.getDecl())) if (isAssumedToBeNotEmitted(cast(fd), isDevice)) return true; return false; } const auto *vd = cast(gd.getDecl()); if (const auto *fd = dyn_cast(vd)) if (isAssumedToBeNotEmitted(cast(fd), isDevice)) return true; // Do not emit function if it is not marked as declare target. if (OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(vd) || alreadyEmittedTargetDecls.count(vd) != 0) return false; // We must also host functions that contain target regions, // because the omp.target ops are nested inside the host function rather than // being outlined early. The containsTargetRegion check handles this. if (const auto *fd = dyn_cast(vd)) if (fd->doesThisDeclarationHaveABody() && containsTargetRegion(fd->getBody())) return false; return true; } bool CIRGenOpenMPRuntime::emitTargetGlobalVariable(GlobalDecl gd) { if (isAssumedToBeNotEmitted(cast(gd.getDecl()), cgm.getLangOpts().OpenMPIsTargetDevice)) return true; if (!cgm.getLangOpts().OpenMPIsTargetDevice) return false; // We do not need to scan for target regions since there's not early // outlining like in OGCG, they will be emitted as omp.target ops instead. const auto *vd = cast(gd.getDecl()); // Do not emit variable if it is not marked as declare target. // OGCG also defers link-clause and USM variables here; we emit errorNYI // for those since they are not yet supported. std::optional res = OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(vd); if (!res || *res == OMPDeclareTargetDeclAttr::MT_Link || ((*res == OMPDeclareTargetDeclAttr::MT_To || *res == OMPDeclareTargetDeclAttr::MT_Enter) && false /* NYI: HasRequiresUnifiedSharedMemory */)) { if (res && *res == OMPDeclareTargetDeclAttr::MT_Link) cgm.errorNYI(vd->getSourceRange(), "declare target global variable with link clause"); // OGCG defers these variables for later emission. We skip them for now. return true; } return false; } // Mirrors CGOpenMPRuntime::emitTargetGlobal. bool CIRGenOpenMPRuntime::emitTargetGlobal(GlobalDecl gd) { if (isa(gd.getDecl()) || isa(gd.getDecl())) return emitTargetFunctions(gd); return emitTargetGlobalVariable(gd); } bool CIRGenOpenMPRuntime::markAsGlobalTarget(GlobalDecl gd) { if (!cgm.getLangOpts().OpenMPIsTargetDevice) return true; const auto *d = cast(gd.getDecl()); if (OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(d)) { if (d->hasBody() && alreadyEmittedTargetDecls.count(d) == 0) { auto f = dyn_cast_if_present( cgm.getGlobalValue(cgm.getMangledName(gd))); if (f) return !f.isDeclaration(); return false; } return true; } return !alreadyEmittedTargetDecls.insert(d).second; } void CIRGenOpenMPRuntime::emitDeclareTargetFunction(const FunctionDecl *fd, cir::FuncOp funcOp) { const auto *attr = fd->getAttr(); assert(attr && "expected OMPDeclareTargetDeclAttr"); // Handles the 'indirect' clause here by creating a global variable to hold // the device function address for runtime resolution of indirect calls on // the device. if (std::optional activeAttr = OMPDeclareTargetDeclAttr::getActiveAttr(fd)) if ((*activeAttr)->getIndirect()) cgm.errorNYI(fd->getSourceRange(), "declare target function with indirect clause"); auto declTargetIface = llvm::cast(funcOp.getOperation()); declTargetIface.setDeclareTarget(convertDeviceType(attr->getDevType()), convertCaptureClause(attr->getMapType()), /*automap=*/false); }