//===------ SemaAMDGPU.cpp ------- AMDGPU target-specific routines --------===// // // 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 file implements semantic analysis functions specific to AMDGPU. // //===----------------------------------------------------------------------===// #include "clang/Sema/SemaAMDGPU.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetBuiltins.h" #include "clang/Sema/Ownership.h" #include "clang/Sema/Sema.h" #include "llvm/Support/AtomicOrdering.h" #include namespace clang { SemaAMDGPU::SemaAMDGPU(Sema &S) : SemaBase(S) {} bool SemaAMDGPU::CheckAMDGCNBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { // position of memory order and scope arguments in the builtin unsigned OrderIndex, ScopeIndex; const auto *FD = SemaRef.getCurFunctionDecl(); assert(FD && "AMDGPU builtins should not be used outside of a function"); llvm::StringMap CallerFeatureMap; getASTContext().getFunctionFeatureMap(CallerFeatureMap, FD); bool HasGFX950Insts = Builtin::evaluateRequiredTargetFeatures("gfx950-insts", CallerFeatureMap); switch (BuiltinID) { case AMDGPU::BI__builtin_amdgcn_global_load_lds: { constexpr const int SizeIdx = 2; llvm::APSInt Size; Expr *ArgExpr = TheCall->getArg(SizeIdx); [[maybe_unused]] ExprResult R = SemaRef.VerifyIntegerConstantExpression(ArgExpr, &Size); assert(!R.isInvalid()); switch (Size.getSExtValue()) { case 1: case 2: case 4: return false; case 12: case 16: { if (HasGFX950Insts) return false; [[fallthrough]]; } default: Diag(ArgExpr->getExprLoc(), diag::err_amdgcn_global_load_lds_size_invalid_value) << ArgExpr->getSourceRange(); Diag(ArgExpr->getExprLoc(), diag::note_amdgcn_global_load_lds_size_valid_value) << HasGFX950Insts << ArgExpr->getSourceRange(); return true; } } case AMDGPU::BI__builtin_amdgcn_get_fpenv: case AMDGPU::BI__builtin_amdgcn_set_fpenv: return false; case AMDGPU::BI__builtin_amdgcn_atomic_inc32: case AMDGPU::BI__builtin_amdgcn_atomic_inc64: case AMDGPU::BI__builtin_amdgcn_atomic_dec32: case AMDGPU::BI__builtin_amdgcn_atomic_dec64: OrderIndex = 2; ScopeIndex = 3; break; case AMDGPU::BI__builtin_amdgcn_fence: OrderIndex = 0; ScopeIndex = 1; break; case AMDGPU::BI__builtin_amdgcn_mov_dpp: return checkMovDPPFunctionCall(TheCall, 5, 1); case AMDGPU::BI__builtin_amdgcn_mov_dpp8: return checkMovDPPFunctionCall(TheCall, 2, 1); case AMDGPU::BI__builtin_amdgcn_update_dpp: { return checkMovDPPFunctionCall(TheCall, 6, 2); } default: return false; } ExprResult Arg = TheCall->getArg(OrderIndex); auto ArgExpr = Arg.get(); Expr::EvalResult ArgResult; if (!ArgExpr->EvaluateAsInt(ArgResult, getASTContext())) return Diag(ArgExpr->getExprLoc(), diag::err_typecheck_expect_int) << ArgExpr->getType(); auto Ord = ArgResult.Val.getInt().getZExtValue(); // Check validity of memory ordering as per C11 / C++11's memody model. // Only fence needs check. Atomic dec/inc allow all memory orders. if (!llvm::isValidAtomicOrderingCABI(Ord)) return Diag(ArgExpr->getBeginLoc(), diag::warn_atomic_op_has_invalid_memory_order) << 0 << ArgExpr->getSourceRange(); switch (static_cast(Ord)) { case llvm::AtomicOrderingCABI::relaxed: case llvm::AtomicOrderingCABI::consume: if (BuiltinID == AMDGPU::BI__builtin_amdgcn_fence) return Diag(ArgExpr->getBeginLoc(), diag::warn_atomic_op_has_invalid_memory_order) << 0 << ArgExpr->getSourceRange(); break; case llvm::AtomicOrderingCABI::acquire: case llvm::AtomicOrderingCABI::release: case llvm::AtomicOrderingCABI::acq_rel: case llvm::AtomicOrderingCABI::seq_cst: break; } Arg = TheCall->getArg(ScopeIndex); ArgExpr = Arg.get(); Expr::EvalResult ArgResult1; // Check that sync scope is a constant literal if (!ArgExpr->EvaluateAsConstantExpr(ArgResult1, getASTContext())) return Diag(ArgExpr->getExprLoc(), diag::err_expr_not_string_literal) << ArgExpr->getType(); return false; } bool SemaAMDGPU::checkMovDPPFunctionCall(CallExpr *TheCall, unsigned NumArgs, unsigned NumDataArgs) { assert(NumDataArgs <= 2); if (SemaRef.checkArgCountRange(TheCall, NumArgs, NumArgs)) return true; Expr *Args[2]; QualType ArgTys[2]; for (unsigned I = 0; I != NumDataArgs; ++I) { Args[I] = TheCall->getArg(I); ArgTys[I] = Args[I]->getType(); // TODO: Vectors can also be supported. if (!ArgTys[I]->isArithmeticType() || ArgTys[I]->isAnyComplexType()) { SemaRef.Diag(Args[I]->getBeginLoc(), diag::err_typecheck_cond_expect_int_float) << ArgTys[I] << Args[I]->getSourceRange(); return true; } } if (NumDataArgs < 2) return false; if (getASTContext().hasSameUnqualifiedType(ArgTys[0], ArgTys[1])) return false; if (((ArgTys[0]->isUnsignedIntegerType() && ArgTys[1]->isSignedIntegerType()) || (ArgTys[0]->isSignedIntegerType() && ArgTys[1]->isUnsignedIntegerType())) && getASTContext().getTypeSize(ArgTys[0]) == getASTContext().getTypeSize(ArgTys[1])) return false; SemaRef.Diag(Args[1]->getBeginLoc(), diag::err_typecheck_call_different_arg_types) << ArgTys[0] << ArgTys[1]; return true; } static bool checkAMDGPUFlatWorkGroupSizeArguments(Sema &S, Expr *MinExpr, Expr *MaxExpr, const AMDGPUFlatWorkGroupSizeAttr &Attr) { // Accept template arguments for now as they depend on something else. // We'll get to check them when they eventually get instantiated. if (MinExpr->isValueDependent() || MaxExpr->isValueDependent()) return false; uint32_t Min = 0; if (!S.checkUInt32Argument(Attr, MinExpr, Min, 0)) return true; uint32_t Max = 0; if (!S.checkUInt32Argument(Attr, MaxExpr, Max, 1)) return true; if (Min == 0 && Max != 0) { S.Diag(Attr.getLocation(), diag::err_attribute_argument_invalid) << &Attr << 0; return true; } if (Min > Max) { S.Diag(Attr.getLocation(), diag::err_attribute_argument_invalid) << &Attr << 1; return true; } return false; } AMDGPUFlatWorkGroupSizeAttr * SemaAMDGPU::CreateAMDGPUFlatWorkGroupSizeAttr(const AttributeCommonInfo &CI, Expr *MinExpr, Expr *MaxExpr) { ASTContext &Context = getASTContext(); AMDGPUFlatWorkGroupSizeAttr TmpAttr(Context, CI, MinExpr, MaxExpr); if (checkAMDGPUFlatWorkGroupSizeArguments(SemaRef, MinExpr, MaxExpr, TmpAttr)) return nullptr; return ::new (Context) AMDGPUFlatWorkGroupSizeAttr(Context, CI, MinExpr, MaxExpr); } void SemaAMDGPU::addAMDGPUFlatWorkGroupSizeAttr(Decl *D, const AttributeCommonInfo &CI, Expr *MinExpr, Expr *MaxExpr) { if (auto *Attr = CreateAMDGPUFlatWorkGroupSizeAttr(CI, MinExpr, MaxExpr)) D->addAttr(Attr); } void SemaAMDGPU::handleAMDGPUFlatWorkGroupSizeAttr(Decl *D, const ParsedAttr &AL) { Expr *MinExpr = AL.getArgAsExpr(0); Expr *MaxExpr = AL.getArgAsExpr(1); addAMDGPUFlatWorkGroupSizeAttr(D, AL, MinExpr, MaxExpr); } static bool checkAMDGPUWavesPerEUArguments(Sema &S, Expr *MinExpr, Expr *MaxExpr, const AMDGPUWavesPerEUAttr &Attr) { if (S.DiagnoseUnexpandedParameterPack(MinExpr) || (MaxExpr && S.DiagnoseUnexpandedParameterPack(MaxExpr))) return true; // Accept template arguments for now as they depend on something else. // We'll get to check them when they eventually get instantiated. if (MinExpr->isValueDependent() || (MaxExpr && MaxExpr->isValueDependent())) return false; uint32_t Min = 0; if (!S.checkUInt32Argument(Attr, MinExpr, Min, 0)) return true; uint32_t Max = 0; if (MaxExpr && !S.checkUInt32Argument(Attr, MaxExpr, Max, 1)) return true; if (Min == 0 && Max != 0) { S.Diag(Attr.getLocation(), diag::err_attribute_argument_invalid) << &Attr << 0; return true; } if (Max != 0 && Min > Max) { S.Diag(Attr.getLocation(), diag::err_attribute_argument_invalid) << &Attr << 1; return true; } return false; } AMDGPUWavesPerEUAttr * SemaAMDGPU::CreateAMDGPUWavesPerEUAttr(const AttributeCommonInfo &CI, Expr *MinExpr, Expr *MaxExpr) { ASTContext &Context = getASTContext(); AMDGPUWavesPerEUAttr TmpAttr(Context, CI, MinExpr, MaxExpr); if (checkAMDGPUWavesPerEUArguments(SemaRef, MinExpr, MaxExpr, TmpAttr)) return nullptr; return ::new (Context) AMDGPUWavesPerEUAttr(Context, CI, MinExpr, MaxExpr); } void SemaAMDGPU::addAMDGPUWavesPerEUAttr(Decl *D, const AttributeCommonInfo &CI, Expr *MinExpr, Expr *MaxExpr) { if (auto *Attr = CreateAMDGPUWavesPerEUAttr(CI, MinExpr, MaxExpr)) D->addAttr(Attr); } void SemaAMDGPU::handleAMDGPUWavesPerEUAttr(Decl *D, const ParsedAttr &AL) { if (!AL.checkAtLeastNumArgs(SemaRef, 1) || !AL.checkAtMostNumArgs(SemaRef, 2)) return; Expr *MinExpr = AL.getArgAsExpr(0); Expr *MaxExpr = (AL.getNumArgs() > 1) ? AL.getArgAsExpr(1) : nullptr; addAMDGPUWavesPerEUAttr(D, AL, MinExpr, MaxExpr); } void SemaAMDGPU::handleAMDGPUNumSGPRAttr(Decl *D, const ParsedAttr &AL) { uint32_t NumSGPR = 0; Expr *NumSGPRExpr = AL.getArgAsExpr(0); if (!SemaRef.checkUInt32Argument(AL, NumSGPRExpr, NumSGPR)) return; D->addAttr(::new (getASTContext()) AMDGPUNumSGPRAttr(getASTContext(), AL, NumSGPR)); } void SemaAMDGPU::handleAMDGPUNumVGPRAttr(Decl *D, const ParsedAttr &AL) { uint32_t NumVGPR = 0; Expr *NumVGPRExpr = AL.getArgAsExpr(0); if (!SemaRef.checkUInt32Argument(AL, NumVGPRExpr, NumVGPR)) return; D->addAttr(::new (getASTContext()) AMDGPUNumVGPRAttr(getASTContext(), AL, NumVGPR)); } static bool checkAMDGPUMaxNumWorkGroupsArguments(Sema &S, Expr *XExpr, Expr *YExpr, Expr *ZExpr, const AMDGPUMaxNumWorkGroupsAttr &Attr) { if (S.DiagnoseUnexpandedParameterPack(XExpr) || (YExpr && S.DiagnoseUnexpandedParameterPack(YExpr)) || (ZExpr && S.DiagnoseUnexpandedParameterPack(ZExpr))) return true; // Accept template arguments for now as they depend on something else. // We'll get to check them when they eventually get instantiated. if (XExpr->isValueDependent() || (YExpr && YExpr->isValueDependent()) || (ZExpr && ZExpr->isValueDependent())) return false; uint32_t NumWG = 0; Expr *Exprs[3] = {XExpr, YExpr, ZExpr}; for (int i = 0; i < 3; i++) { if (Exprs[i]) { if (!S.checkUInt32Argument(Attr, Exprs[i], NumWG, i, /*StrictlyUnsigned=*/true)) return true; if (NumWG == 0) { S.Diag(Attr.getLoc(), diag::err_attribute_argument_is_zero) << &Attr << Exprs[i]->getSourceRange(); return true; } } } return false; } AMDGPUMaxNumWorkGroupsAttr *SemaAMDGPU::CreateAMDGPUMaxNumWorkGroupsAttr( const AttributeCommonInfo &CI, Expr *XExpr, Expr *YExpr, Expr *ZExpr) { ASTContext &Context = getASTContext(); AMDGPUMaxNumWorkGroupsAttr TmpAttr(Context, CI, XExpr, YExpr, ZExpr); if (checkAMDGPUMaxNumWorkGroupsArguments(SemaRef, XExpr, YExpr, ZExpr, TmpAttr)) return nullptr; return ::new (Context) AMDGPUMaxNumWorkGroupsAttr(Context, CI, XExpr, YExpr, ZExpr); } void SemaAMDGPU::addAMDGPUMaxNumWorkGroupsAttr(Decl *D, const AttributeCommonInfo &CI, Expr *XExpr, Expr *YExpr, Expr *ZExpr) { if (auto *Attr = CreateAMDGPUMaxNumWorkGroupsAttr(CI, XExpr, YExpr, ZExpr)) D->addAttr(Attr); } void SemaAMDGPU::handleAMDGPUMaxNumWorkGroupsAttr(Decl *D, const ParsedAttr &AL) { Expr *YExpr = (AL.getNumArgs() > 1) ? AL.getArgAsExpr(1) : nullptr; Expr *ZExpr = (AL.getNumArgs() > 2) ? AL.getArgAsExpr(2) : nullptr; addAMDGPUMaxNumWorkGroupsAttr(D, AL, AL.getArgAsExpr(0), YExpr, ZExpr); } } // namespace clang