mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-24 21:56:04 +00:00

This is an alternative to https://github.com/llvm/llvm-project/pull/122103 In SPIR-V, private global variables have the Private storage class. This PR adds a new address space which allows frontend to emit variable with this storage class when targeting this backend. This is covered in this proposal: llvm/wg-hlsl@4c9e11a This PR will cause addrspacecast to show up in several cases, like class member functions or assignment. Those will have to be handled in the backend later on, particularly to fixup pointer storage classes in some functions. Before this change, global variable were emitted with the 'Function' storage class, which was wrong.
3472 lines
124 KiB
C++
3472 lines
124 KiB
C++
//===- SemaHLSL.cpp - Semantic Analysis for HLSL constructs ---------------===//
|
|
//
|
|
// 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 implements Semantic Analysis for HLSL constructs.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Sema/SemaHLSL.h"
|
|
#include "clang/AST/ASTConsumer.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Attr.h"
|
|
#include "clang/AST/Attrs.inc"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclBase.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclarationName.h"
|
|
#include "clang/AST/DynamicRecursiveASTVisitor.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/AST/TypeLoc.h"
|
|
#include "clang/Basic/Builtins.h"
|
|
#include "clang/Basic/DiagnosticSema.h"
|
|
#include "clang/Basic/IdentifierTable.h"
|
|
#include "clang/Basic/LLVM.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Basic/Specifiers.h"
|
|
#include "clang/Basic/TargetInfo.h"
|
|
#include "clang/Sema/Initialization.h"
|
|
#include "clang/Sema/ParsedAttr.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/Template.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/Twine.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/DXILABI.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
#include <cstddef>
|
|
#include <iterator>
|
|
#include <utility>
|
|
|
|
using namespace clang;
|
|
using RegisterType = HLSLResourceBindingAttr::RegisterType;
|
|
|
|
static CXXRecordDecl *createHostLayoutStruct(Sema &S,
|
|
CXXRecordDecl *StructDecl);
|
|
|
|
static RegisterType getRegisterType(ResourceClass RC) {
|
|
switch (RC) {
|
|
case ResourceClass::SRV:
|
|
return RegisterType::SRV;
|
|
case ResourceClass::UAV:
|
|
return RegisterType::UAV;
|
|
case ResourceClass::CBuffer:
|
|
return RegisterType::CBuffer;
|
|
case ResourceClass::Sampler:
|
|
return RegisterType::Sampler;
|
|
}
|
|
llvm_unreachable("unexpected ResourceClass value");
|
|
}
|
|
|
|
// Converts the first letter of string Slot to RegisterType.
|
|
// Returns false if the letter does not correspond to a valid register type.
|
|
static bool convertToRegisterType(StringRef Slot, RegisterType *RT) {
|
|
assert(RT != nullptr);
|
|
switch (Slot[0]) {
|
|
case 't':
|
|
case 'T':
|
|
*RT = RegisterType::SRV;
|
|
return true;
|
|
case 'u':
|
|
case 'U':
|
|
*RT = RegisterType::UAV;
|
|
return true;
|
|
case 'b':
|
|
case 'B':
|
|
*RT = RegisterType::CBuffer;
|
|
return true;
|
|
case 's':
|
|
case 'S':
|
|
*RT = RegisterType::Sampler;
|
|
return true;
|
|
case 'c':
|
|
case 'C':
|
|
*RT = RegisterType::C;
|
|
return true;
|
|
case 'i':
|
|
case 'I':
|
|
*RT = RegisterType::I;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static ResourceClass getResourceClass(RegisterType RT) {
|
|
switch (RT) {
|
|
case RegisterType::SRV:
|
|
return ResourceClass::SRV;
|
|
case RegisterType::UAV:
|
|
return ResourceClass::UAV;
|
|
case RegisterType::CBuffer:
|
|
return ResourceClass::CBuffer;
|
|
case RegisterType::Sampler:
|
|
return ResourceClass::Sampler;
|
|
case RegisterType::C:
|
|
case RegisterType::I:
|
|
// Deliberately falling through to the unreachable below.
|
|
break;
|
|
}
|
|
llvm_unreachable("unexpected RegisterType value");
|
|
}
|
|
|
|
DeclBindingInfo *ResourceBindings::addDeclBindingInfo(const VarDecl *VD,
|
|
ResourceClass ResClass) {
|
|
assert(getDeclBindingInfo(VD, ResClass) == nullptr &&
|
|
"DeclBindingInfo already added");
|
|
assert(!hasBindingInfoForDecl(VD) || BindingsList.back().Decl == VD);
|
|
// VarDecl may have multiple entries for different resource classes.
|
|
// DeclToBindingListIndex stores the index of the first binding we saw
|
|
// for this decl. If there are any additional ones then that index
|
|
// shouldn't be updated.
|
|
DeclToBindingListIndex.try_emplace(VD, BindingsList.size());
|
|
return &BindingsList.emplace_back(VD, ResClass);
|
|
}
|
|
|
|
DeclBindingInfo *ResourceBindings::getDeclBindingInfo(const VarDecl *VD,
|
|
ResourceClass ResClass) {
|
|
auto Entry = DeclToBindingListIndex.find(VD);
|
|
if (Entry != DeclToBindingListIndex.end()) {
|
|
for (unsigned Index = Entry->getSecond();
|
|
Index < BindingsList.size() && BindingsList[Index].Decl == VD;
|
|
++Index) {
|
|
if (BindingsList[Index].ResClass == ResClass)
|
|
return &BindingsList[Index];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool ResourceBindings::hasBindingInfoForDecl(const VarDecl *VD) const {
|
|
return DeclToBindingListIndex.contains(VD);
|
|
}
|
|
|
|
SemaHLSL::SemaHLSL(Sema &S) : SemaBase(S) {}
|
|
|
|
Decl *SemaHLSL::ActOnStartBuffer(Scope *BufferScope, bool CBuffer,
|
|
SourceLocation KwLoc, IdentifierInfo *Ident,
|
|
SourceLocation IdentLoc,
|
|
SourceLocation LBrace) {
|
|
// For anonymous namespace, take the location of the left brace.
|
|
DeclContext *LexicalParent = SemaRef.getCurLexicalContext();
|
|
HLSLBufferDecl *Result = HLSLBufferDecl::Create(
|
|
getASTContext(), LexicalParent, CBuffer, KwLoc, Ident, IdentLoc, LBrace);
|
|
|
|
// if CBuffer is false, then it's a TBuffer
|
|
auto RC = CBuffer ? llvm::hlsl::ResourceClass::CBuffer
|
|
: llvm::hlsl::ResourceClass::SRV;
|
|
Result->addAttr(HLSLResourceClassAttr::CreateImplicit(getASTContext(), RC));
|
|
|
|
SemaRef.PushOnScopeChains(Result, BufferScope);
|
|
SemaRef.PushDeclContext(BufferScope, Result);
|
|
|
|
return Result;
|
|
}
|
|
|
|
static unsigned calculateLegacyCbufferFieldAlign(const ASTContext &Context,
|
|
QualType T) {
|
|
// Arrays and Structs are always aligned to new buffer rows
|
|
if (T->isArrayType() || T->isStructureType())
|
|
return 16;
|
|
|
|
// Vectors are aligned to the type they contain
|
|
if (const VectorType *VT = T->getAs<VectorType>())
|
|
return calculateLegacyCbufferFieldAlign(Context, VT->getElementType());
|
|
|
|
assert(Context.getTypeSize(T) <= 64 &&
|
|
"Scalar bit widths larger than 64 not supported");
|
|
|
|
// Scalar types are aligned to their byte width
|
|
return Context.getTypeSize(T) / 8;
|
|
}
|
|
|
|
// Calculate the size of a legacy cbuffer type in bytes based on
|
|
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules
|
|
static unsigned calculateLegacyCbufferSize(const ASTContext &Context,
|
|
QualType T) {
|
|
constexpr unsigned CBufferAlign = 16;
|
|
if (const RecordType *RT = T->getAs<RecordType>()) {
|
|
unsigned Size = 0;
|
|
const RecordDecl *RD = RT->getDecl();
|
|
for (const FieldDecl *Field : RD->fields()) {
|
|
QualType Ty = Field->getType();
|
|
unsigned FieldSize = calculateLegacyCbufferSize(Context, Ty);
|
|
unsigned FieldAlign = calculateLegacyCbufferFieldAlign(Context, Ty);
|
|
|
|
// If the field crosses the row boundary after alignment it drops to the
|
|
// next row
|
|
unsigned AlignSize = llvm::alignTo(Size, FieldAlign);
|
|
if ((AlignSize % CBufferAlign) + FieldSize > CBufferAlign) {
|
|
FieldAlign = CBufferAlign;
|
|
}
|
|
|
|
Size = llvm::alignTo(Size, FieldAlign);
|
|
Size += FieldSize;
|
|
}
|
|
return Size;
|
|
}
|
|
|
|
if (const ConstantArrayType *AT = Context.getAsConstantArrayType(T)) {
|
|
unsigned ElementCount = AT->getSize().getZExtValue();
|
|
if (ElementCount == 0)
|
|
return 0;
|
|
|
|
unsigned ElementSize =
|
|
calculateLegacyCbufferSize(Context, AT->getElementType());
|
|
unsigned AlignedElementSize = llvm::alignTo(ElementSize, CBufferAlign);
|
|
return AlignedElementSize * (ElementCount - 1) + ElementSize;
|
|
}
|
|
|
|
if (const VectorType *VT = T->getAs<VectorType>()) {
|
|
unsigned ElementCount = VT->getNumElements();
|
|
unsigned ElementSize =
|
|
calculateLegacyCbufferSize(Context, VT->getElementType());
|
|
return ElementSize * ElementCount;
|
|
}
|
|
|
|
return Context.getTypeSize(T) / 8;
|
|
}
|
|
|
|
// Validate packoffset:
|
|
// - if packoffset it used it must be set on all declarations inside the buffer
|
|
// - packoffset ranges must not overlap
|
|
static void validatePackoffset(Sema &S, HLSLBufferDecl *BufDecl) {
|
|
llvm::SmallVector<std::pair<VarDecl *, HLSLPackOffsetAttr *>> PackOffsetVec;
|
|
|
|
// Make sure the packoffset annotations are either on all declarations
|
|
// or on none.
|
|
bool HasPackOffset = false;
|
|
bool HasNonPackOffset = false;
|
|
for (auto *Field : BufDecl->buffer_decls()) {
|
|
VarDecl *Var = dyn_cast<VarDecl>(Field);
|
|
if (!Var)
|
|
continue;
|
|
if (Field->hasAttr<HLSLPackOffsetAttr>()) {
|
|
PackOffsetVec.emplace_back(Var, Field->getAttr<HLSLPackOffsetAttr>());
|
|
HasPackOffset = true;
|
|
} else {
|
|
HasNonPackOffset = true;
|
|
}
|
|
}
|
|
|
|
if (!HasPackOffset)
|
|
return;
|
|
|
|
if (HasNonPackOffset)
|
|
S.Diag(BufDecl->getLocation(), diag::warn_hlsl_packoffset_mix);
|
|
|
|
// Make sure there is no overlap in packoffset - sort PackOffsetVec by offset
|
|
// and compare adjacent values.
|
|
bool IsValid = true;
|
|
ASTContext &Context = S.getASTContext();
|
|
std::sort(PackOffsetVec.begin(), PackOffsetVec.end(),
|
|
[](const std::pair<VarDecl *, HLSLPackOffsetAttr *> &LHS,
|
|
const std::pair<VarDecl *, HLSLPackOffsetAttr *> &RHS) {
|
|
return LHS.second->getOffsetInBytes() <
|
|
RHS.second->getOffsetInBytes();
|
|
});
|
|
for (unsigned i = 0; i < PackOffsetVec.size() - 1; i++) {
|
|
VarDecl *Var = PackOffsetVec[i].first;
|
|
HLSLPackOffsetAttr *Attr = PackOffsetVec[i].second;
|
|
unsigned Size = calculateLegacyCbufferSize(Context, Var->getType());
|
|
unsigned Begin = Attr->getOffsetInBytes();
|
|
unsigned End = Begin + Size;
|
|
unsigned NextBegin = PackOffsetVec[i + 1].second->getOffsetInBytes();
|
|
if (End > NextBegin) {
|
|
VarDecl *NextVar = PackOffsetVec[i + 1].first;
|
|
S.Diag(NextVar->getLocation(), diag::err_hlsl_packoffset_overlap)
|
|
<< NextVar << Var;
|
|
IsValid = false;
|
|
}
|
|
}
|
|
BufDecl->setHasValidPackoffset(IsValid);
|
|
}
|
|
|
|
// Returns true if the array has a zero size = if any of the dimensions is 0
|
|
static bool isZeroSizedArray(const ConstantArrayType *CAT) {
|
|
while (CAT && !CAT->isZeroSize())
|
|
CAT = dyn_cast<ConstantArrayType>(
|
|
CAT->getElementType()->getUnqualifiedDesugaredType());
|
|
return CAT != nullptr;
|
|
}
|
|
|
|
// Returns true if the record type is an HLSL resource class or an array of
|
|
// resource classes
|
|
static bool isResourceRecordTypeOrArrayOf(const Type *Ty) {
|
|
while (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(Ty))
|
|
Ty = CAT->getArrayElementTypeNoTypeQual();
|
|
return HLSLAttributedResourceType::findHandleTypeOnResource(Ty) != nullptr;
|
|
}
|
|
|
|
// Returns true if the type is a leaf element type that is not valid to be
|
|
// included in HLSL Buffer, such as a resource class, empty struct, zero-sized
|
|
// array, or a builtin intangible type. Returns false it is a valid leaf element
|
|
// type or if it is a record type that needs to be inspected further.
|
|
static bool isInvalidConstantBufferLeafElementType(const Type *Ty) {
|
|
Ty = Ty->getUnqualifiedDesugaredType();
|
|
if (isResourceRecordTypeOrArrayOf(Ty))
|
|
return true;
|
|
if (Ty->isRecordType())
|
|
return Ty->getAsCXXRecordDecl()->isEmpty();
|
|
if (Ty->isConstantArrayType() &&
|
|
isZeroSizedArray(cast<ConstantArrayType>(Ty)))
|
|
return true;
|
|
if (Ty->isHLSLBuiltinIntangibleType() || Ty->isHLSLAttributedResourceType())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Returns true if the struct contains at least one element that prevents it
|
|
// from being included inside HLSL Buffer as is, such as an intangible type,
|
|
// empty struct, or zero-sized array. If it does, a new implicit layout struct
|
|
// needs to be created for HLSL Buffer use that will exclude these unwanted
|
|
// declarations (see createHostLayoutStruct function).
|
|
static bool requiresImplicitBufferLayoutStructure(const CXXRecordDecl *RD) {
|
|
if (RD->getTypeForDecl()->isHLSLIntangibleType() || RD->isEmpty())
|
|
return true;
|
|
// check fields
|
|
for (const FieldDecl *Field : RD->fields()) {
|
|
QualType Ty = Field->getType();
|
|
if (isInvalidConstantBufferLeafElementType(Ty.getTypePtr()))
|
|
return true;
|
|
if (Ty->isRecordType() &&
|
|
requiresImplicitBufferLayoutStructure(Ty->getAsCXXRecordDecl()))
|
|
return true;
|
|
}
|
|
// check bases
|
|
for (const CXXBaseSpecifier &Base : RD->bases())
|
|
if (requiresImplicitBufferLayoutStructure(
|
|
Base.getType()->getAsCXXRecordDecl()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static CXXRecordDecl *findRecordDeclInContext(IdentifierInfo *II,
|
|
DeclContext *DC) {
|
|
CXXRecordDecl *RD = nullptr;
|
|
for (NamedDecl *Decl :
|
|
DC->getNonTransparentContext()->lookup(DeclarationName(II))) {
|
|
if (CXXRecordDecl *FoundRD = dyn_cast<CXXRecordDecl>(Decl)) {
|
|
assert(RD == nullptr &&
|
|
"there should be at most 1 record by a given name in a scope");
|
|
RD = FoundRD;
|
|
}
|
|
}
|
|
return RD;
|
|
}
|
|
|
|
// Creates a name for buffer layout struct using the provide name base.
|
|
// If the name must be unique (not previously defined), a suffix is added
|
|
// until a unique name is found.
|
|
static IdentifierInfo *getHostLayoutStructName(Sema &S, NamedDecl *BaseDecl,
|
|
bool MustBeUnique) {
|
|
ASTContext &AST = S.getASTContext();
|
|
|
|
IdentifierInfo *NameBaseII = BaseDecl->getIdentifier();
|
|
llvm::SmallString<64> Name("__cblayout_");
|
|
if (NameBaseII) {
|
|
Name.append(NameBaseII->getName());
|
|
} else {
|
|
// anonymous struct
|
|
Name.append("anon");
|
|
MustBeUnique = true;
|
|
}
|
|
|
|
size_t NameLength = Name.size();
|
|
IdentifierInfo *II = &AST.Idents.get(Name, tok::TokenKind::identifier);
|
|
if (!MustBeUnique)
|
|
return II;
|
|
|
|
unsigned suffix = 0;
|
|
while (true) {
|
|
if (suffix != 0) {
|
|
Name.append("_");
|
|
Name.append(llvm::Twine(suffix).str());
|
|
II = &AST.Idents.get(Name, tok::TokenKind::identifier);
|
|
}
|
|
if (!findRecordDeclInContext(II, BaseDecl->getDeclContext()))
|
|
return II;
|
|
// declaration with that name already exists - increment suffix and try
|
|
// again until unique name is found
|
|
suffix++;
|
|
Name.truncate(NameLength);
|
|
};
|
|
}
|
|
|
|
// Creates a field declaration of given name and type for HLSL buffer layout
|
|
// struct. Returns nullptr if the type cannot be use in HLSL Buffer layout.
|
|
static FieldDecl *createFieldForHostLayoutStruct(Sema &S, const Type *Ty,
|
|
IdentifierInfo *II,
|
|
CXXRecordDecl *LayoutStruct) {
|
|
if (isInvalidConstantBufferLeafElementType(Ty))
|
|
return nullptr;
|
|
|
|
if (Ty->isRecordType()) {
|
|
CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
|
|
if (requiresImplicitBufferLayoutStructure(RD)) {
|
|
RD = createHostLayoutStruct(S, RD);
|
|
if (!RD)
|
|
return nullptr;
|
|
Ty = RD->getTypeForDecl();
|
|
}
|
|
}
|
|
|
|
QualType QT = QualType(Ty, 0);
|
|
ASTContext &AST = S.getASTContext();
|
|
TypeSourceInfo *TSI = AST.getTrivialTypeSourceInfo(QT, SourceLocation());
|
|
auto *Field = FieldDecl::Create(AST, LayoutStruct, SourceLocation(),
|
|
SourceLocation(), II, QT, TSI, nullptr, false,
|
|
InClassInitStyle::ICIS_NoInit);
|
|
Field->setAccess(AccessSpecifier::AS_public);
|
|
return Field;
|
|
}
|
|
|
|
// Creates host layout struct for a struct included in HLSL Buffer.
|
|
// The layout struct will include only fields that are allowed in HLSL buffer.
|
|
// These fields will be filtered out:
|
|
// - resource classes
|
|
// - empty structs
|
|
// - zero-sized arrays
|
|
// Returns nullptr if the resulting layout struct would be empty.
|
|
static CXXRecordDecl *createHostLayoutStruct(Sema &S,
|
|
CXXRecordDecl *StructDecl) {
|
|
assert(requiresImplicitBufferLayoutStructure(StructDecl) &&
|
|
"struct is already HLSL buffer compatible");
|
|
|
|
ASTContext &AST = S.getASTContext();
|
|
DeclContext *DC = StructDecl->getDeclContext();
|
|
IdentifierInfo *II = getHostLayoutStructName(S, StructDecl, false);
|
|
|
|
// reuse existing if the layout struct if it already exists
|
|
if (CXXRecordDecl *RD = findRecordDeclInContext(II, DC))
|
|
return RD;
|
|
|
|
CXXRecordDecl *LS =
|
|
CXXRecordDecl::Create(AST, TagDecl::TagKind::Struct, DC, SourceLocation(),
|
|
SourceLocation(), II);
|
|
LS->setImplicit(true);
|
|
LS->addAttr(PackedAttr::CreateImplicit(AST));
|
|
LS->startDefinition();
|
|
|
|
// copy base struct, create HLSL Buffer compatible version if needed
|
|
if (unsigned NumBases = StructDecl->getNumBases()) {
|
|
assert(NumBases == 1 && "HLSL supports only one base type");
|
|
(void)NumBases;
|
|
CXXBaseSpecifier Base = *StructDecl->bases_begin();
|
|
CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl();
|
|
if (requiresImplicitBufferLayoutStructure(BaseDecl)) {
|
|
BaseDecl = createHostLayoutStruct(S, BaseDecl);
|
|
if (BaseDecl) {
|
|
TypeSourceInfo *TSI = AST.getTrivialTypeSourceInfo(
|
|
QualType(BaseDecl->getTypeForDecl(), 0));
|
|
Base = CXXBaseSpecifier(SourceRange(), false, StructDecl->isClass(),
|
|
AS_none, TSI, SourceLocation());
|
|
}
|
|
}
|
|
if (BaseDecl) {
|
|
const CXXBaseSpecifier *BasesArray[1] = {&Base};
|
|
LS->setBases(BasesArray, 1);
|
|
}
|
|
}
|
|
|
|
// filter struct fields
|
|
for (const FieldDecl *FD : StructDecl->fields()) {
|
|
const Type *Ty = FD->getType()->getUnqualifiedDesugaredType();
|
|
if (FieldDecl *NewFD =
|
|
createFieldForHostLayoutStruct(S, Ty, FD->getIdentifier(), LS))
|
|
LS->addDecl(NewFD);
|
|
}
|
|
LS->completeDefinition();
|
|
|
|
if (LS->field_empty() && LS->getNumBases() == 0)
|
|
return nullptr;
|
|
|
|
DC->addDecl(LS);
|
|
return LS;
|
|
}
|
|
|
|
// Creates host layout struct for HLSL Buffer. The struct will include only
|
|
// fields of types that are allowed in HLSL buffer and it will filter out:
|
|
// - static or groupshared variable declarations
|
|
// - resource classes
|
|
// - empty structs
|
|
// - zero-sized arrays
|
|
// - non-variable declarations
|
|
// The layout struct will be added to the HLSLBufferDecl declarations.
|
|
void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
|
|
ASTContext &AST = S.getASTContext();
|
|
IdentifierInfo *II = getHostLayoutStructName(S, BufDecl, true);
|
|
|
|
CXXRecordDecl *LS =
|
|
CXXRecordDecl::Create(AST, TagDecl::TagKind::Struct, BufDecl,
|
|
SourceLocation(), SourceLocation(), II);
|
|
LS->addAttr(PackedAttr::CreateImplicit(AST));
|
|
LS->setImplicit(true);
|
|
LS->startDefinition();
|
|
|
|
for (Decl *D : BufDecl->buffer_decls()) {
|
|
VarDecl *VD = dyn_cast<VarDecl>(D);
|
|
if (!VD || VD->getStorageClass() == SC_Static ||
|
|
VD->getType().getAddressSpace() == LangAS::hlsl_groupshared)
|
|
continue;
|
|
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
|
|
if (FieldDecl *FD =
|
|
createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS)) {
|
|
// add the field decl to the layout struct
|
|
LS->addDecl(FD);
|
|
// update address space of the original decl to hlsl_constant
|
|
QualType NewTy =
|
|
AST.getAddrSpaceQualType(VD->getType(), LangAS::hlsl_constant);
|
|
VD->setType(NewTy);
|
|
}
|
|
}
|
|
LS->completeDefinition();
|
|
BufDecl->addLayoutStruct(LS);
|
|
}
|
|
|
|
// Handle end of cbuffer/tbuffer declaration
|
|
void SemaHLSL::ActOnFinishBuffer(Decl *Dcl, SourceLocation RBrace) {
|
|
auto *BufDecl = cast<HLSLBufferDecl>(Dcl);
|
|
BufDecl->setRBraceLoc(RBrace);
|
|
|
|
validatePackoffset(SemaRef, BufDecl);
|
|
|
|
// create buffer layout struct
|
|
createHostLayoutStructForBuffer(SemaRef, BufDecl);
|
|
|
|
SemaRef.PopDeclContext();
|
|
}
|
|
|
|
HLSLNumThreadsAttr *SemaHLSL::mergeNumThreadsAttr(Decl *D,
|
|
const AttributeCommonInfo &AL,
|
|
int X, int Y, int Z) {
|
|
if (HLSLNumThreadsAttr *NT = D->getAttr<HLSLNumThreadsAttr>()) {
|
|
if (NT->getX() != X || NT->getY() != Y || NT->getZ() != Z) {
|
|
Diag(NT->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL;
|
|
Diag(AL.getLoc(), diag::note_conflicting_attribute);
|
|
}
|
|
return nullptr;
|
|
}
|
|
return ::new (getASTContext())
|
|
HLSLNumThreadsAttr(getASTContext(), AL, X, Y, Z);
|
|
}
|
|
|
|
HLSLWaveSizeAttr *SemaHLSL::mergeWaveSizeAttr(Decl *D,
|
|
const AttributeCommonInfo &AL,
|
|
int Min, int Max, int Preferred,
|
|
int SpelledArgsCount) {
|
|
if (HLSLWaveSizeAttr *WS = D->getAttr<HLSLWaveSizeAttr>()) {
|
|
if (WS->getMin() != Min || WS->getMax() != Max ||
|
|
WS->getPreferred() != Preferred ||
|
|
WS->getSpelledArgsCount() != SpelledArgsCount) {
|
|
Diag(WS->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL;
|
|
Diag(AL.getLoc(), diag::note_conflicting_attribute);
|
|
}
|
|
return nullptr;
|
|
}
|
|
HLSLWaveSizeAttr *Result = ::new (getASTContext())
|
|
HLSLWaveSizeAttr(getASTContext(), AL, Min, Max, Preferred);
|
|
Result->setSpelledArgsCount(SpelledArgsCount);
|
|
return Result;
|
|
}
|
|
|
|
HLSLShaderAttr *
|
|
SemaHLSL::mergeShaderAttr(Decl *D, const AttributeCommonInfo &AL,
|
|
llvm::Triple::EnvironmentType ShaderType) {
|
|
if (HLSLShaderAttr *NT = D->getAttr<HLSLShaderAttr>()) {
|
|
if (NT->getType() != ShaderType) {
|
|
Diag(NT->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL;
|
|
Diag(AL.getLoc(), diag::note_conflicting_attribute);
|
|
}
|
|
return nullptr;
|
|
}
|
|
return HLSLShaderAttr::Create(getASTContext(), ShaderType, AL);
|
|
}
|
|
|
|
HLSLParamModifierAttr *
|
|
SemaHLSL::mergeParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
|
|
HLSLParamModifierAttr::Spelling Spelling) {
|
|
// We can only merge an `in` attribute with an `out` attribute. All other
|
|
// combinations of duplicated attributes are ill-formed.
|
|
if (HLSLParamModifierAttr *PA = D->getAttr<HLSLParamModifierAttr>()) {
|
|
if ((PA->isIn() && Spelling == HLSLParamModifierAttr::Keyword_out) ||
|
|
(PA->isOut() && Spelling == HLSLParamModifierAttr::Keyword_in)) {
|
|
D->dropAttr<HLSLParamModifierAttr>();
|
|
SourceRange AdjustedRange = {PA->getLocation(), AL.getRange().getEnd()};
|
|
return HLSLParamModifierAttr::Create(
|
|
getASTContext(), /*MergedSpelling=*/true, AdjustedRange,
|
|
HLSLParamModifierAttr::Keyword_inout);
|
|
}
|
|
Diag(AL.getLoc(), diag::err_hlsl_duplicate_parameter_modifier) << AL;
|
|
Diag(PA->getLocation(), diag::note_conflicting_attribute);
|
|
return nullptr;
|
|
}
|
|
return HLSLParamModifierAttr::Create(getASTContext(), AL);
|
|
}
|
|
|
|
void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
|
|
auto &TargetInfo = getASTContext().getTargetInfo();
|
|
|
|
if (FD->getName() != TargetInfo.getTargetOpts().HLSLEntry)
|
|
return;
|
|
|
|
llvm::Triple::EnvironmentType Env = TargetInfo.getTriple().getEnvironment();
|
|
if (HLSLShaderAttr::isValidShaderType(Env) && Env != llvm::Triple::Library) {
|
|
if (const auto *Shader = FD->getAttr<HLSLShaderAttr>()) {
|
|
// The entry point is already annotated - check that it matches the
|
|
// triple.
|
|
if (Shader->getType() != Env) {
|
|
Diag(Shader->getLocation(), diag::err_hlsl_entry_shader_attr_mismatch)
|
|
<< Shader;
|
|
FD->setInvalidDecl();
|
|
}
|
|
} else {
|
|
// Implicitly add the shader attribute if the entry function isn't
|
|
// explicitly annotated.
|
|
FD->addAttr(HLSLShaderAttr::CreateImplicit(getASTContext(), Env,
|
|
FD->getBeginLoc()));
|
|
}
|
|
} else {
|
|
switch (Env) {
|
|
case llvm::Triple::UnknownEnvironment:
|
|
case llvm::Triple::Library:
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unhandled environment in triple");
|
|
}
|
|
}
|
|
}
|
|
|
|
void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
|
|
const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
|
|
assert(ShaderAttr && "Entry point has no shader attribute");
|
|
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
|
|
auto &TargetInfo = getASTContext().getTargetInfo();
|
|
VersionTuple Ver = TargetInfo.getTriple().getOSVersion();
|
|
switch (ST) {
|
|
case llvm::Triple::Pixel:
|
|
case llvm::Triple::Vertex:
|
|
case llvm::Triple::Geometry:
|
|
case llvm::Triple::Hull:
|
|
case llvm::Triple::Domain:
|
|
case llvm::Triple::RayGeneration:
|
|
case llvm::Triple::Intersection:
|
|
case llvm::Triple::AnyHit:
|
|
case llvm::Triple::ClosestHit:
|
|
case llvm::Triple::Miss:
|
|
case llvm::Triple::Callable:
|
|
if (const auto *NT = FD->getAttr<HLSLNumThreadsAttr>()) {
|
|
DiagnoseAttrStageMismatch(NT, ST,
|
|
{llvm::Triple::Compute,
|
|
llvm::Triple::Amplification,
|
|
llvm::Triple::Mesh});
|
|
FD->setInvalidDecl();
|
|
}
|
|
if (const auto *WS = FD->getAttr<HLSLWaveSizeAttr>()) {
|
|
DiagnoseAttrStageMismatch(WS, ST,
|
|
{llvm::Triple::Compute,
|
|
llvm::Triple::Amplification,
|
|
llvm::Triple::Mesh});
|
|
FD->setInvalidDecl();
|
|
}
|
|
break;
|
|
|
|
case llvm::Triple::Compute:
|
|
case llvm::Triple::Amplification:
|
|
case llvm::Triple::Mesh:
|
|
if (!FD->hasAttr<HLSLNumThreadsAttr>()) {
|
|
Diag(FD->getLocation(), diag::err_hlsl_missing_numthreads)
|
|
<< llvm::Triple::getEnvironmentTypeName(ST);
|
|
FD->setInvalidDecl();
|
|
}
|
|
if (const auto *WS = FD->getAttr<HLSLWaveSizeAttr>()) {
|
|
if (Ver < VersionTuple(6, 6)) {
|
|
Diag(WS->getLocation(), diag::err_hlsl_attribute_in_wrong_shader_model)
|
|
<< WS << "6.6";
|
|
FD->setInvalidDecl();
|
|
} else if (WS->getSpelledArgsCount() > 1 && Ver < VersionTuple(6, 8)) {
|
|
Diag(
|
|
WS->getLocation(),
|
|
diag::err_hlsl_attribute_number_arguments_insufficient_shader_model)
|
|
<< WS << WS->getSpelledArgsCount() << "6.8";
|
|
FD->setInvalidDecl();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unhandled environment in triple");
|
|
}
|
|
|
|
for (ParmVarDecl *Param : FD->parameters()) {
|
|
if (const auto *AnnotationAttr = Param->getAttr<HLSLAnnotationAttr>()) {
|
|
CheckSemanticAnnotation(FD, Param, AnnotationAttr);
|
|
} else {
|
|
// FIXME: Handle struct parameters where annotations are on struct fields.
|
|
// See: https://github.com/llvm/llvm-project/issues/57875
|
|
Diag(FD->getLocation(), diag::err_hlsl_missing_semantic_annotation);
|
|
Diag(Param->getLocation(), diag::note_previous_decl) << Param;
|
|
FD->setInvalidDecl();
|
|
}
|
|
}
|
|
// FIXME: Verify return type semantic annotation.
|
|
}
|
|
|
|
void SemaHLSL::CheckSemanticAnnotation(
|
|
FunctionDecl *EntryPoint, const Decl *Param,
|
|
const HLSLAnnotationAttr *AnnotationAttr) {
|
|
auto *ShaderAttr = EntryPoint->getAttr<HLSLShaderAttr>();
|
|
assert(ShaderAttr && "Entry point has no shader attribute");
|
|
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
|
|
|
|
switch (AnnotationAttr->getKind()) {
|
|
case attr::HLSLSV_DispatchThreadID:
|
|
case attr::HLSLSV_GroupIndex:
|
|
case attr::HLSLSV_GroupThreadID:
|
|
case attr::HLSLSV_GroupID:
|
|
if (ST == llvm::Triple::Compute)
|
|
return;
|
|
DiagnoseAttrStageMismatch(AnnotationAttr, ST, {llvm::Triple::Compute});
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unknown HLSLAnnotationAttr");
|
|
}
|
|
}
|
|
|
|
void SemaHLSL::DiagnoseAttrStageMismatch(
|
|
const Attr *A, llvm::Triple::EnvironmentType Stage,
|
|
std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages) {
|
|
SmallVector<StringRef, 8> StageStrings;
|
|
llvm::transform(AllowedStages, std::back_inserter(StageStrings),
|
|
[](llvm::Triple::EnvironmentType ST) {
|
|
return StringRef(
|
|
HLSLShaderAttr::ConvertEnvironmentTypeToStr(ST));
|
|
});
|
|
Diag(A->getLoc(), diag::err_hlsl_attr_unsupported_in_stage)
|
|
<< A->getAttrName() << llvm::Triple::getEnvironmentTypeName(Stage)
|
|
<< (AllowedStages.size() != 1) << join(StageStrings, ", ");
|
|
}
|
|
|
|
template <CastKind Kind>
|
|
static void castVector(Sema &S, ExprResult &E, QualType &Ty, unsigned Sz) {
|
|
if (const auto *VTy = Ty->getAs<VectorType>())
|
|
Ty = VTy->getElementType();
|
|
Ty = S.getASTContext().getExtVectorType(Ty, Sz);
|
|
E = S.ImpCastExprToType(E.get(), Ty, Kind);
|
|
}
|
|
|
|
template <CastKind Kind>
|
|
static QualType castElement(Sema &S, ExprResult &E, QualType Ty) {
|
|
E = S.ImpCastExprToType(E.get(), Ty, Kind);
|
|
return Ty;
|
|
}
|
|
|
|
static QualType handleFloatVectorBinOpConversion(
|
|
Sema &SemaRef, ExprResult &LHS, ExprResult &RHS, QualType LHSType,
|
|
QualType RHSType, QualType LElTy, QualType RElTy, bool IsCompAssign) {
|
|
bool LHSFloat = LElTy->isRealFloatingType();
|
|
bool RHSFloat = RElTy->isRealFloatingType();
|
|
|
|
if (LHSFloat && RHSFloat) {
|
|
if (IsCompAssign ||
|
|
SemaRef.getASTContext().getFloatingTypeOrder(LElTy, RElTy) > 0)
|
|
return castElement<CK_FloatingCast>(SemaRef, RHS, LHSType);
|
|
|
|
return castElement<CK_FloatingCast>(SemaRef, LHS, RHSType);
|
|
}
|
|
|
|
if (LHSFloat)
|
|
return castElement<CK_IntegralToFloating>(SemaRef, RHS, LHSType);
|
|
|
|
assert(RHSFloat);
|
|
if (IsCompAssign)
|
|
return castElement<clang::CK_FloatingToIntegral>(SemaRef, RHS, LHSType);
|
|
|
|
return castElement<CK_IntegralToFloating>(SemaRef, LHS, RHSType);
|
|
}
|
|
|
|
static QualType handleIntegerVectorBinOpConversion(
|
|
Sema &SemaRef, ExprResult &LHS, ExprResult &RHS, QualType LHSType,
|
|
QualType RHSType, QualType LElTy, QualType RElTy, bool IsCompAssign) {
|
|
|
|
int IntOrder = SemaRef.Context.getIntegerTypeOrder(LElTy, RElTy);
|
|
bool LHSSigned = LElTy->hasSignedIntegerRepresentation();
|
|
bool RHSSigned = RElTy->hasSignedIntegerRepresentation();
|
|
auto &Ctx = SemaRef.getASTContext();
|
|
|
|
// If both types have the same signedness, use the higher ranked type.
|
|
if (LHSSigned == RHSSigned) {
|
|
if (IsCompAssign || IntOrder >= 0)
|
|
return castElement<CK_IntegralCast>(SemaRef, RHS, LHSType);
|
|
|
|
return castElement<CK_IntegralCast>(SemaRef, LHS, RHSType);
|
|
}
|
|
|
|
// If the unsigned type has greater than or equal rank of the signed type, use
|
|
// the unsigned type.
|
|
if (IntOrder != (LHSSigned ? 1 : -1)) {
|
|
if (IsCompAssign || RHSSigned)
|
|
return castElement<CK_IntegralCast>(SemaRef, RHS, LHSType);
|
|
return castElement<CK_IntegralCast>(SemaRef, LHS, RHSType);
|
|
}
|
|
|
|
// At this point the signed type has higher rank than the unsigned type, which
|
|
// means it will be the same size or bigger. If the signed type is bigger, it
|
|
// can represent all the values of the unsigned type, so select it.
|
|
if (Ctx.getIntWidth(LElTy) != Ctx.getIntWidth(RElTy)) {
|
|
if (IsCompAssign || LHSSigned)
|
|
return castElement<CK_IntegralCast>(SemaRef, RHS, LHSType);
|
|
return castElement<CK_IntegralCast>(SemaRef, LHS, RHSType);
|
|
}
|
|
|
|
// This is a bit of an odd duck case in HLSL. It shouldn't happen, but can due
|
|
// to C/C++ leaking through. The place this happens today is long vs long
|
|
// long. When arguments are vector<unsigned long, N> and vector<long long, N>,
|
|
// the long long has higher rank than long even though they are the same size.
|
|
|
|
// If this is a compound assignment cast the right hand side to the left hand
|
|
// side's type.
|
|
if (IsCompAssign)
|
|
return castElement<CK_IntegralCast>(SemaRef, RHS, LHSType);
|
|
|
|
// If this isn't a compound assignment we convert to unsigned long long.
|
|
QualType ElTy = Ctx.getCorrespondingUnsignedType(LHSSigned ? LElTy : RElTy);
|
|
QualType NewTy = Ctx.getExtVectorType(
|
|
ElTy, RHSType->castAs<VectorType>()->getNumElements());
|
|
(void)castElement<CK_IntegralCast>(SemaRef, RHS, NewTy);
|
|
|
|
return castElement<CK_IntegralCast>(SemaRef, LHS, NewTy);
|
|
}
|
|
|
|
static CastKind getScalarCastKind(ASTContext &Ctx, QualType DestTy,
|
|
QualType SrcTy) {
|
|
if (DestTy->isRealFloatingType() && SrcTy->isRealFloatingType())
|
|
return CK_FloatingCast;
|
|
if (DestTy->isIntegralType(Ctx) && SrcTy->isIntegralType(Ctx))
|
|
return CK_IntegralCast;
|
|
if (DestTy->isRealFloatingType())
|
|
return CK_IntegralToFloating;
|
|
assert(SrcTy->isRealFloatingType() && DestTy->isIntegralType(Ctx));
|
|
return CK_FloatingToIntegral;
|
|
}
|
|
|
|
QualType SemaHLSL::handleVectorBinOpConversion(ExprResult &LHS, ExprResult &RHS,
|
|
QualType LHSType,
|
|
QualType RHSType,
|
|
bool IsCompAssign) {
|
|
const auto *LVecTy = LHSType->getAs<VectorType>();
|
|
const auto *RVecTy = RHSType->getAs<VectorType>();
|
|
auto &Ctx = getASTContext();
|
|
|
|
// If the LHS is not a vector and this is a compound assignment, we truncate
|
|
// the argument to a scalar then convert it to the LHS's type.
|
|
if (!LVecTy && IsCompAssign) {
|
|
QualType RElTy = RHSType->castAs<VectorType>()->getElementType();
|
|
RHS = SemaRef.ImpCastExprToType(RHS.get(), RElTy, CK_HLSLVectorTruncation);
|
|
RHSType = RHS.get()->getType();
|
|
if (Ctx.hasSameUnqualifiedType(LHSType, RHSType))
|
|
return LHSType;
|
|
RHS = SemaRef.ImpCastExprToType(RHS.get(), LHSType,
|
|
getScalarCastKind(Ctx, LHSType, RHSType));
|
|
return LHSType;
|
|
}
|
|
|
|
unsigned EndSz = std::numeric_limits<unsigned>::max();
|
|
unsigned LSz = 0;
|
|
if (LVecTy)
|
|
LSz = EndSz = LVecTy->getNumElements();
|
|
if (RVecTy)
|
|
EndSz = std::min(RVecTy->getNumElements(), EndSz);
|
|
assert(EndSz != std::numeric_limits<unsigned>::max() &&
|
|
"one of the above should have had a value");
|
|
|
|
// In a compound assignment, the left operand does not change type, the right
|
|
// operand is converted to the type of the left operand.
|
|
if (IsCompAssign && LSz != EndSz) {
|
|
Diag(LHS.get()->getBeginLoc(),
|
|
diag::err_hlsl_vector_compound_assignment_truncation)
|
|
<< LHSType << RHSType;
|
|
return QualType();
|
|
}
|
|
|
|
if (RVecTy && RVecTy->getNumElements() > EndSz)
|
|
castVector<CK_HLSLVectorTruncation>(SemaRef, RHS, RHSType, EndSz);
|
|
if (!IsCompAssign && LVecTy && LVecTy->getNumElements() > EndSz)
|
|
castVector<CK_HLSLVectorTruncation>(SemaRef, LHS, LHSType, EndSz);
|
|
|
|
if (!RVecTy)
|
|
castVector<CK_VectorSplat>(SemaRef, RHS, RHSType, EndSz);
|
|
if (!IsCompAssign && !LVecTy)
|
|
castVector<CK_VectorSplat>(SemaRef, LHS, LHSType, EndSz);
|
|
|
|
// If we're at the same type after resizing we can stop here.
|
|
if (Ctx.hasSameUnqualifiedType(LHSType, RHSType))
|
|
return Ctx.getCommonSugaredType(LHSType, RHSType);
|
|
|
|
QualType LElTy = LHSType->castAs<VectorType>()->getElementType();
|
|
QualType RElTy = RHSType->castAs<VectorType>()->getElementType();
|
|
|
|
// Handle conversion for floating point vectors.
|
|
if (LElTy->isRealFloatingType() || RElTy->isRealFloatingType())
|
|
return handleFloatVectorBinOpConversion(SemaRef, LHS, RHS, LHSType, RHSType,
|
|
LElTy, RElTy, IsCompAssign);
|
|
|
|
assert(LElTy->isIntegralType(Ctx) && RElTy->isIntegralType(Ctx) &&
|
|
"HLSL Vectors can only contain integer or floating point types");
|
|
return handleIntegerVectorBinOpConversion(SemaRef, LHS, RHS, LHSType, RHSType,
|
|
LElTy, RElTy, IsCompAssign);
|
|
}
|
|
|
|
void SemaHLSL::emitLogicalOperatorFixIt(Expr *LHS, Expr *RHS,
|
|
BinaryOperatorKind Opc) {
|
|
assert((Opc == BO_LOr || Opc == BO_LAnd) &&
|
|
"Called with non-logical operator");
|
|
llvm::SmallVector<char, 256> Buff;
|
|
llvm::raw_svector_ostream OS(Buff);
|
|
PrintingPolicy PP(SemaRef.getLangOpts());
|
|
StringRef NewFnName = Opc == BO_LOr ? "or" : "and";
|
|
OS << NewFnName << "(";
|
|
LHS->printPretty(OS, nullptr, PP);
|
|
OS << ", ";
|
|
RHS->printPretty(OS, nullptr, PP);
|
|
OS << ")";
|
|
SourceRange FullRange = SourceRange(LHS->getBeginLoc(), RHS->getEndLoc());
|
|
SemaRef.Diag(LHS->getBeginLoc(), diag::note_function_suggestion)
|
|
<< NewFnName << FixItHint::CreateReplacement(FullRange, OS.str());
|
|
}
|
|
|
|
void SemaHLSL::handleNumThreadsAttr(Decl *D, const ParsedAttr &AL) {
|
|
llvm::VersionTuple SMVersion =
|
|
getASTContext().getTargetInfo().getTriple().getOSVersion();
|
|
uint32_t ZMax = 1024;
|
|
uint32_t ThreadMax = 1024;
|
|
if (SMVersion.getMajor() <= 4) {
|
|
ZMax = 1;
|
|
ThreadMax = 768;
|
|
} else if (SMVersion.getMajor() == 5) {
|
|
ZMax = 64;
|
|
ThreadMax = 1024;
|
|
}
|
|
|
|
uint32_t X;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), X))
|
|
return;
|
|
if (X > 1024) {
|
|
Diag(AL.getArgAsExpr(0)->getExprLoc(),
|
|
diag::err_hlsl_numthreads_argument_oor)
|
|
<< 0 << 1024;
|
|
return;
|
|
}
|
|
uint32_t Y;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(1), Y))
|
|
return;
|
|
if (Y > 1024) {
|
|
Diag(AL.getArgAsExpr(1)->getExprLoc(),
|
|
diag::err_hlsl_numthreads_argument_oor)
|
|
<< 1 << 1024;
|
|
return;
|
|
}
|
|
uint32_t Z;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(2), Z))
|
|
return;
|
|
if (Z > ZMax) {
|
|
SemaRef.Diag(AL.getArgAsExpr(2)->getExprLoc(),
|
|
diag::err_hlsl_numthreads_argument_oor)
|
|
<< 2 << ZMax;
|
|
return;
|
|
}
|
|
|
|
if (X * Y * Z > ThreadMax) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_numthreads_invalid) << ThreadMax;
|
|
return;
|
|
}
|
|
|
|
HLSLNumThreadsAttr *NewAttr = mergeNumThreadsAttr(D, AL, X, Y, Z);
|
|
if (NewAttr)
|
|
D->addAttr(NewAttr);
|
|
}
|
|
|
|
static bool isValidWaveSizeValue(unsigned Value) {
|
|
return llvm::isPowerOf2_32(Value) && Value >= 4 && Value <= 128;
|
|
}
|
|
|
|
void SemaHLSL::handleWaveSizeAttr(Decl *D, const ParsedAttr &AL) {
|
|
// validate that the wavesize argument is a power of 2 between 4 and 128
|
|
// inclusive
|
|
unsigned SpelledArgsCount = AL.getNumArgs();
|
|
if (SpelledArgsCount == 0 || SpelledArgsCount > 3)
|
|
return;
|
|
|
|
uint32_t Min;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Min))
|
|
return;
|
|
|
|
uint32_t Max = 0;
|
|
if (SpelledArgsCount > 1 &&
|
|
!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(1), Max))
|
|
return;
|
|
|
|
uint32_t Preferred = 0;
|
|
if (SpelledArgsCount > 2 &&
|
|
!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(2), Preferred))
|
|
return;
|
|
|
|
if (SpelledArgsCount > 2) {
|
|
if (!isValidWaveSizeValue(Preferred)) {
|
|
Diag(AL.getArgAsExpr(2)->getExprLoc(),
|
|
diag::err_attribute_power_of_two_in_range)
|
|
<< AL << llvm::dxil::MinWaveSize << llvm::dxil::MaxWaveSize
|
|
<< Preferred;
|
|
return;
|
|
}
|
|
// Preferred not in range.
|
|
if (Preferred < Min || Preferred > Max) {
|
|
Diag(AL.getArgAsExpr(2)->getExprLoc(),
|
|
diag::err_attribute_power_of_two_in_range)
|
|
<< AL << Min << Max << Preferred;
|
|
return;
|
|
}
|
|
} else if (SpelledArgsCount > 1) {
|
|
if (!isValidWaveSizeValue(Max)) {
|
|
Diag(AL.getArgAsExpr(1)->getExprLoc(),
|
|
diag::err_attribute_power_of_two_in_range)
|
|
<< AL << llvm::dxil::MinWaveSize << llvm::dxil::MaxWaveSize << Max;
|
|
return;
|
|
}
|
|
if (Max < Min) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_invalid) << AL << 1;
|
|
return;
|
|
} else if (Max == Min) {
|
|
Diag(AL.getLoc(), diag::warn_attr_min_eq_max) << AL;
|
|
}
|
|
} else {
|
|
if (!isValidWaveSizeValue(Min)) {
|
|
Diag(AL.getArgAsExpr(0)->getExprLoc(),
|
|
diag::err_attribute_power_of_two_in_range)
|
|
<< AL << llvm::dxil::MinWaveSize << llvm::dxil::MaxWaveSize << Min;
|
|
return;
|
|
}
|
|
}
|
|
|
|
HLSLWaveSizeAttr *NewAttr =
|
|
mergeWaveSizeAttr(D, AL, Min, Max, Preferred, SpelledArgsCount);
|
|
if (NewAttr)
|
|
D->addAttr(NewAttr);
|
|
}
|
|
|
|
bool SemaHLSL::diagnoseInputIDType(QualType T, const ParsedAttr &AL) {
|
|
const auto *VT = T->getAs<VectorType>();
|
|
|
|
if (!T->hasUnsignedIntegerRepresentation() ||
|
|
(VT && VT->getNumElements() > 3)) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
|
|
<< AL << "uint/uint2/uint3";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
|
|
auto *VD = cast<ValueDecl>(D);
|
|
if (!diagnoseInputIDType(VD->getType(), AL))
|
|
return;
|
|
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLSV_DispatchThreadIDAttr(getASTContext(), AL));
|
|
}
|
|
|
|
void SemaHLSL::handleSV_GroupThreadIDAttr(Decl *D, const ParsedAttr &AL) {
|
|
auto *VD = cast<ValueDecl>(D);
|
|
if (!diagnoseInputIDType(VD->getType(), AL))
|
|
return;
|
|
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLSV_GroupThreadIDAttr(getASTContext(), AL));
|
|
}
|
|
|
|
void SemaHLSL::handleSV_GroupIDAttr(Decl *D, const ParsedAttr &AL) {
|
|
auto *VD = cast<ValueDecl>(D);
|
|
if (!diagnoseInputIDType(VD->getType(), AL))
|
|
return;
|
|
|
|
D->addAttr(::new (getASTContext()) HLSLSV_GroupIDAttr(getASTContext(), AL));
|
|
}
|
|
|
|
void SemaHLSL::handlePackOffsetAttr(Decl *D, const ParsedAttr &AL) {
|
|
if (!isa<VarDecl>(D) || !isa<HLSLBufferDecl>(D->getDeclContext())) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_ast_node)
|
|
<< AL << "shader constant in a constant buffer";
|
|
return;
|
|
}
|
|
|
|
uint32_t SubComponent;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), SubComponent))
|
|
return;
|
|
uint32_t Component;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(1), Component))
|
|
return;
|
|
|
|
QualType T = cast<VarDecl>(D)->getType().getCanonicalType();
|
|
// Check if T is an array or struct type.
|
|
// TODO: mark matrix type as aggregate type.
|
|
bool IsAggregateTy = (T->isArrayType() || T->isStructureType());
|
|
|
|
// Check Component is valid for T.
|
|
if (Component) {
|
|
unsigned Size = getASTContext().getTypeSize(T);
|
|
if (IsAggregateTy || Size > 128) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_packoffset_cross_reg_boundary);
|
|
return;
|
|
} else {
|
|
// Make sure Component + sizeof(T) <= 4.
|
|
if ((Component * 32 + Size) > 128) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_packoffset_cross_reg_boundary);
|
|
return;
|
|
}
|
|
QualType EltTy = T;
|
|
if (const auto *VT = T->getAs<VectorType>())
|
|
EltTy = VT->getElementType();
|
|
unsigned Align = getASTContext().getTypeAlign(EltTy);
|
|
if (Align > 32 && Component == 1) {
|
|
// NOTE: Component 3 will hit err_hlsl_packoffset_cross_reg_boundary.
|
|
// So we only need to check Component 1 here.
|
|
Diag(AL.getLoc(), diag::err_hlsl_packoffset_alignment_mismatch)
|
|
<< Align << EltTy;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
D->addAttr(::new (getASTContext()) HLSLPackOffsetAttr(
|
|
getASTContext(), AL, SubComponent, Component));
|
|
}
|
|
|
|
void SemaHLSL::handleShaderAttr(Decl *D, const ParsedAttr &AL) {
|
|
StringRef Str;
|
|
SourceLocation ArgLoc;
|
|
if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
|
|
return;
|
|
|
|
llvm::Triple::EnvironmentType ShaderType;
|
|
if (!HLSLShaderAttr::ConvertStrToEnvironmentType(Str, ShaderType)) {
|
|
Diag(AL.getLoc(), diag::warn_attribute_type_not_supported)
|
|
<< AL << Str << ArgLoc;
|
|
return;
|
|
}
|
|
|
|
// FIXME: check function match the shader stage.
|
|
|
|
HLSLShaderAttr *NewAttr = mergeShaderAttr(D, AL, ShaderType);
|
|
if (NewAttr)
|
|
D->addAttr(NewAttr);
|
|
}
|
|
|
|
bool clang::CreateHLSLAttributedResourceType(
|
|
Sema &S, QualType Wrapped, ArrayRef<const Attr *> AttrList,
|
|
QualType &ResType, HLSLAttributedResourceLocInfo *LocInfo) {
|
|
assert(AttrList.size() && "expected list of resource attributes");
|
|
|
|
QualType ContainedTy = QualType();
|
|
TypeSourceInfo *ContainedTyInfo = nullptr;
|
|
SourceLocation LocBegin = AttrList[0]->getRange().getBegin();
|
|
SourceLocation LocEnd = AttrList[0]->getRange().getEnd();
|
|
|
|
HLSLAttributedResourceType::Attributes ResAttrs;
|
|
|
|
bool HasResourceClass = false;
|
|
for (const Attr *A : AttrList) {
|
|
if (!A)
|
|
continue;
|
|
LocEnd = A->getRange().getEnd();
|
|
switch (A->getKind()) {
|
|
case attr::HLSLResourceClass: {
|
|
ResourceClass RC = cast<HLSLResourceClassAttr>(A)->getResourceClass();
|
|
if (HasResourceClass) {
|
|
S.Diag(A->getLocation(), ResAttrs.ResourceClass == RC
|
|
? diag::warn_duplicate_attribute_exact
|
|
: diag::warn_duplicate_attribute)
|
|
<< A;
|
|
return false;
|
|
}
|
|
ResAttrs.ResourceClass = RC;
|
|
HasResourceClass = true;
|
|
break;
|
|
}
|
|
case attr::HLSLROV:
|
|
if (ResAttrs.IsROV) {
|
|
S.Diag(A->getLocation(), diag::warn_duplicate_attribute_exact) << A;
|
|
return false;
|
|
}
|
|
ResAttrs.IsROV = true;
|
|
break;
|
|
case attr::HLSLRawBuffer:
|
|
if (ResAttrs.RawBuffer) {
|
|
S.Diag(A->getLocation(), diag::warn_duplicate_attribute_exact) << A;
|
|
return false;
|
|
}
|
|
ResAttrs.RawBuffer = true;
|
|
break;
|
|
case attr::HLSLContainedType: {
|
|
const HLSLContainedTypeAttr *CTAttr = cast<HLSLContainedTypeAttr>(A);
|
|
QualType Ty = CTAttr->getType();
|
|
if (!ContainedTy.isNull()) {
|
|
S.Diag(A->getLocation(), ContainedTy == Ty
|
|
? diag::warn_duplicate_attribute_exact
|
|
: diag::warn_duplicate_attribute)
|
|
<< A;
|
|
return false;
|
|
}
|
|
ContainedTy = Ty;
|
|
ContainedTyInfo = CTAttr->getTypeLoc();
|
|
break;
|
|
}
|
|
default:
|
|
llvm_unreachable("unhandled resource attribute type");
|
|
}
|
|
}
|
|
|
|
if (!HasResourceClass) {
|
|
S.Diag(AttrList.back()->getRange().getEnd(),
|
|
diag::err_hlsl_missing_resource_class);
|
|
return false;
|
|
}
|
|
|
|
ResType = S.getASTContext().getHLSLAttributedResourceType(
|
|
Wrapped, ContainedTy, ResAttrs);
|
|
|
|
if (LocInfo && ContainedTyInfo) {
|
|
LocInfo->Range = SourceRange(LocBegin, LocEnd);
|
|
LocInfo->ContainedTyInfo = ContainedTyInfo;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Validates and creates an HLSL attribute that is applied as type attribute on
|
|
// HLSL resource. The attributes are collected in HLSLResourcesTypeAttrs and at
|
|
// the end of the declaration they are applied to the declaration type by
|
|
// wrapping it in HLSLAttributedResourceType.
|
|
bool SemaHLSL::handleResourceTypeAttr(QualType T, const ParsedAttr &AL) {
|
|
// only allow resource type attributes on intangible types
|
|
if (!T->isHLSLResourceType()) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_attribute_needs_intangible_type)
|
|
<< AL << getASTContext().HLSLResourceTy;
|
|
return false;
|
|
}
|
|
|
|
// validate number of arguments
|
|
if (!AL.checkExactlyNumArgs(SemaRef, AL.getMinArgs()))
|
|
return false;
|
|
|
|
Attr *A = nullptr;
|
|
switch (AL.getKind()) {
|
|
case ParsedAttr::AT_HLSLResourceClass: {
|
|
if (!AL.isArgIdent(0)) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
|
<< AL << AANT_ArgumentIdentifier;
|
|
return false;
|
|
}
|
|
|
|
IdentifierLoc *Loc = AL.getArgAsIdent(0);
|
|
StringRef Identifier = Loc->Ident->getName();
|
|
SourceLocation ArgLoc = Loc->Loc;
|
|
|
|
// Validate resource class value
|
|
ResourceClass RC;
|
|
if (!HLSLResourceClassAttr::ConvertStrToResourceClass(Identifier, RC)) {
|
|
Diag(ArgLoc, diag::warn_attribute_type_not_supported)
|
|
<< "ResourceClass" << Identifier;
|
|
return false;
|
|
}
|
|
A = HLSLResourceClassAttr::Create(getASTContext(), RC, AL.getLoc());
|
|
break;
|
|
}
|
|
|
|
case ParsedAttr::AT_HLSLROV:
|
|
A = HLSLROVAttr::Create(getASTContext(), AL.getLoc());
|
|
break;
|
|
|
|
case ParsedAttr::AT_HLSLRawBuffer:
|
|
A = HLSLRawBufferAttr::Create(getASTContext(), AL.getLoc());
|
|
break;
|
|
|
|
case ParsedAttr::AT_HLSLContainedType: {
|
|
if (AL.getNumArgs() != 1 && !AL.hasParsedType()) {
|
|
Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
|
|
return false;
|
|
}
|
|
|
|
TypeSourceInfo *TSI = nullptr;
|
|
QualType QT = SemaRef.GetTypeFromParser(AL.getTypeArg(), &TSI);
|
|
assert(TSI && "no type source info for attribute argument");
|
|
if (SemaRef.RequireCompleteType(TSI->getTypeLoc().getBeginLoc(), QT,
|
|
diag::err_incomplete_type))
|
|
return false;
|
|
A = HLSLContainedTypeAttr::Create(getASTContext(), TSI, AL.getLoc());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
llvm_unreachable("unhandled HLSL attribute");
|
|
}
|
|
|
|
HLSLResourcesTypeAttrs.emplace_back(A);
|
|
return true;
|
|
}
|
|
|
|
// Combines all resource type attributes and creates HLSLAttributedResourceType.
|
|
QualType SemaHLSL::ProcessResourceTypeAttributes(QualType CurrentType) {
|
|
if (!HLSLResourcesTypeAttrs.size())
|
|
return CurrentType;
|
|
|
|
QualType QT = CurrentType;
|
|
HLSLAttributedResourceLocInfo LocInfo;
|
|
if (CreateHLSLAttributedResourceType(SemaRef, CurrentType,
|
|
HLSLResourcesTypeAttrs, QT, &LocInfo)) {
|
|
const HLSLAttributedResourceType *RT =
|
|
cast<HLSLAttributedResourceType>(QT.getTypePtr());
|
|
|
|
// Temporarily store TypeLoc information for the new type.
|
|
// It will be transferred to HLSLAttributesResourceTypeLoc
|
|
// shortly after the type is created by TypeSpecLocFiller which
|
|
// will call the TakeLocForHLSLAttribute method below.
|
|
LocsForHLSLAttributedResources.insert(std::pair(RT, LocInfo));
|
|
}
|
|
HLSLResourcesTypeAttrs.clear();
|
|
return QT;
|
|
}
|
|
|
|
// Returns source location for the HLSLAttributedResourceType
|
|
HLSLAttributedResourceLocInfo
|
|
SemaHLSL::TakeLocForHLSLAttribute(const HLSLAttributedResourceType *RT) {
|
|
HLSLAttributedResourceLocInfo LocInfo = {};
|
|
auto I = LocsForHLSLAttributedResources.find(RT);
|
|
if (I != LocsForHLSLAttributedResources.end()) {
|
|
LocInfo = I->second;
|
|
LocsForHLSLAttributedResources.erase(I);
|
|
return LocInfo;
|
|
}
|
|
LocInfo.Range = SourceRange();
|
|
return LocInfo;
|
|
}
|
|
|
|
// Walks though the global variable declaration, collects all resource binding
|
|
// requirements and adds them to Bindings
|
|
void SemaHLSL::collectResourceBindingsOnUserRecordDecl(const VarDecl *VD,
|
|
const RecordType *RT) {
|
|
const RecordDecl *RD = RT->getDecl();
|
|
for (FieldDecl *FD : RD->fields()) {
|
|
const Type *Ty = FD->getType()->getUnqualifiedDesugaredType();
|
|
|
|
// Unwrap arrays
|
|
// FIXME: Calculate array size while unwrapping
|
|
assert(!Ty->isIncompleteArrayType() &&
|
|
"incomplete arrays inside user defined types are not supported");
|
|
while (Ty->isConstantArrayType()) {
|
|
const ConstantArrayType *CAT = cast<ConstantArrayType>(Ty);
|
|
Ty = CAT->getElementType()->getUnqualifiedDesugaredType();
|
|
}
|
|
|
|
if (!Ty->isRecordType())
|
|
continue;
|
|
|
|
if (const HLSLAttributedResourceType *AttrResType =
|
|
HLSLAttributedResourceType::findHandleTypeOnResource(Ty)) {
|
|
// Add a new DeclBindingInfo to Bindings if it does not already exist
|
|
ResourceClass RC = AttrResType->getAttrs().ResourceClass;
|
|
DeclBindingInfo *DBI = Bindings.getDeclBindingInfo(VD, RC);
|
|
if (!DBI)
|
|
Bindings.addDeclBindingInfo(VD, RC);
|
|
} else if (const RecordType *RT = dyn_cast<RecordType>(Ty)) {
|
|
// Recursively scan embedded struct or class; it would be nice to do this
|
|
// without recursion, but tricky to correctly calculate the size of the
|
|
// binding, which is something we are probably going to need to do later
|
|
// on. Hopefully nesting of structs in structs too many levels is
|
|
// unlikely.
|
|
collectResourceBindingsOnUserRecordDecl(VD, RT);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Diagnose localized register binding errors for a single binding; does not
|
|
// diagnose resource binding on user record types, that will be done later
|
|
// in processResourceBindingOnDecl based on the information collected in
|
|
// collectResourceBindingsOnVarDecl.
|
|
// Returns false if the register binding is not valid.
|
|
static bool DiagnoseLocalRegisterBinding(Sema &S, SourceLocation &ArgLoc,
|
|
Decl *D, RegisterType RegType,
|
|
bool SpecifiedSpace) {
|
|
int RegTypeNum = static_cast<int>(RegType);
|
|
|
|
// check if the decl type is groupshared
|
|
if (D->hasAttr<HLSLGroupSharedAddressSpaceAttr>()) {
|
|
S.Diag(ArgLoc, diag::err_hlsl_binding_type_mismatch) << RegTypeNum;
|
|
return false;
|
|
}
|
|
|
|
// Cbuffers and Tbuffers are HLSLBufferDecl types
|
|
if (HLSLBufferDecl *CBufferOrTBuffer = dyn_cast<HLSLBufferDecl>(D)) {
|
|
ResourceClass RC = CBufferOrTBuffer->isCBuffer() ? ResourceClass::CBuffer
|
|
: ResourceClass::SRV;
|
|
if (RegType == getRegisterType(RC))
|
|
return true;
|
|
|
|
S.Diag(D->getLocation(), diag::err_hlsl_binding_type_mismatch)
|
|
<< RegTypeNum;
|
|
return false;
|
|
}
|
|
|
|
// Samplers, UAVs, and SRVs are VarDecl types
|
|
assert(isa<VarDecl>(D) && "D is expected to be VarDecl or HLSLBufferDecl");
|
|
VarDecl *VD = cast<VarDecl>(D);
|
|
|
|
// Resource
|
|
if (const HLSLAttributedResourceType *AttrResType =
|
|
HLSLAttributedResourceType::findHandleTypeOnResource(
|
|
VD->getType().getTypePtr())) {
|
|
if (RegType == getRegisterType(AttrResType->getAttrs().ResourceClass))
|
|
return true;
|
|
|
|
S.Diag(D->getLocation(), diag::err_hlsl_binding_type_mismatch)
|
|
<< RegTypeNum;
|
|
return false;
|
|
}
|
|
|
|
const clang::Type *Ty = VD->getType().getTypePtr();
|
|
while (Ty->isArrayType())
|
|
Ty = Ty->getArrayElementTypeNoTypeQual();
|
|
|
|
// Basic types
|
|
if (Ty->isArithmeticType() || Ty->isVectorType()) {
|
|
bool DeclaredInCOrTBuffer = isa<HLSLBufferDecl>(D->getDeclContext());
|
|
if (SpecifiedSpace && !DeclaredInCOrTBuffer)
|
|
S.Diag(ArgLoc, diag::err_hlsl_space_on_global_constant);
|
|
|
|
if (!DeclaredInCOrTBuffer && (Ty->isIntegralType(S.getASTContext()) ||
|
|
Ty->isFloatingType() || Ty->isVectorType())) {
|
|
// Register annotation on default constant buffer declaration ($Globals)
|
|
if (RegType == RegisterType::CBuffer)
|
|
S.Diag(ArgLoc, diag::warn_hlsl_deprecated_register_type_b);
|
|
else if (RegType != RegisterType::C)
|
|
S.Diag(ArgLoc, diag::err_hlsl_binding_type_mismatch) << RegTypeNum;
|
|
else
|
|
return true;
|
|
} else {
|
|
if (RegType == RegisterType::C)
|
|
S.Diag(ArgLoc, diag::warn_hlsl_register_type_c_packoffset);
|
|
else
|
|
S.Diag(ArgLoc, diag::err_hlsl_binding_type_mismatch) << RegTypeNum;
|
|
}
|
|
return false;
|
|
}
|
|
if (Ty->isRecordType())
|
|
// RecordTypes will be diagnosed in processResourceBindingOnDecl
|
|
// that is called from ActOnVariableDeclarator
|
|
return true;
|
|
|
|
// Anything else is an error
|
|
S.Diag(ArgLoc, diag::err_hlsl_binding_type_mismatch) << RegTypeNum;
|
|
return false;
|
|
}
|
|
|
|
static bool ValidateMultipleRegisterAnnotations(Sema &S, Decl *TheDecl,
|
|
RegisterType regType) {
|
|
// make sure that there are no two register annotations
|
|
// applied to the decl with the same register type
|
|
bool RegisterTypesDetected[5] = {false};
|
|
RegisterTypesDetected[static_cast<int>(regType)] = true;
|
|
|
|
for (auto it = TheDecl->attr_begin(); it != TheDecl->attr_end(); ++it) {
|
|
if (HLSLResourceBindingAttr *attr =
|
|
dyn_cast<HLSLResourceBindingAttr>(*it)) {
|
|
|
|
RegisterType otherRegType = attr->getRegisterType();
|
|
if (RegisterTypesDetected[static_cast<int>(otherRegType)]) {
|
|
int otherRegTypeNum = static_cast<int>(otherRegType);
|
|
S.Diag(TheDecl->getLocation(),
|
|
diag::err_hlsl_duplicate_register_annotation)
|
|
<< otherRegTypeNum;
|
|
return false;
|
|
}
|
|
RegisterTypesDetected[static_cast<int>(otherRegType)] = true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
|
|
Decl *D, RegisterType RegType,
|
|
bool SpecifiedSpace) {
|
|
|
|
// exactly one of these two types should be set
|
|
assert(((isa<VarDecl>(D) && !isa<HLSLBufferDecl>(D)) ||
|
|
(!isa<VarDecl>(D) && isa<HLSLBufferDecl>(D))) &&
|
|
"expecting VarDecl or HLSLBufferDecl");
|
|
|
|
// check if the declaration contains resource matching the register type
|
|
if (!DiagnoseLocalRegisterBinding(S, ArgLoc, D, RegType, SpecifiedSpace))
|
|
return false;
|
|
|
|
// next, if multiple register annotations exist, check that none conflict.
|
|
return ValidateMultipleRegisterAnnotations(S, D, RegType);
|
|
}
|
|
|
|
void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
|
|
if (isa<VarDecl>(TheDecl)) {
|
|
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(),
|
|
cast<ValueDecl>(TheDecl)->getType(),
|
|
diag::err_incomplete_type))
|
|
return;
|
|
}
|
|
StringRef Space = "space0";
|
|
StringRef Slot = "";
|
|
|
|
if (!AL.isArgIdent(0)) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
|
<< AL << AANT_ArgumentIdentifier;
|
|
return;
|
|
}
|
|
|
|
IdentifierLoc *Loc = AL.getArgAsIdent(0);
|
|
StringRef Str = Loc->Ident->getName();
|
|
SourceLocation ArgLoc = Loc->Loc;
|
|
|
|
SourceLocation SpaceArgLoc;
|
|
bool SpecifiedSpace = false;
|
|
if (AL.getNumArgs() == 2) {
|
|
SpecifiedSpace = true;
|
|
Slot = Str;
|
|
if (!AL.isArgIdent(1)) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
|
<< AL << AANT_ArgumentIdentifier;
|
|
return;
|
|
}
|
|
|
|
IdentifierLoc *Loc = AL.getArgAsIdent(1);
|
|
Space = Loc->Ident->getName();
|
|
SpaceArgLoc = Loc->Loc;
|
|
} else {
|
|
Slot = Str;
|
|
}
|
|
|
|
RegisterType RegType;
|
|
unsigned SlotNum = 0;
|
|
unsigned SpaceNum = 0;
|
|
|
|
// Validate.
|
|
if (!Slot.empty()) {
|
|
if (!convertToRegisterType(Slot, &RegType)) {
|
|
Diag(ArgLoc, diag::err_hlsl_binding_type_invalid) << Slot.substr(0, 1);
|
|
return;
|
|
}
|
|
if (RegType == RegisterType::I) {
|
|
Diag(ArgLoc, diag::warn_hlsl_deprecated_register_type_i);
|
|
return;
|
|
}
|
|
|
|
StringRef SlotNumStr = Slot.substr(1);
|
|
if (SlotNumStr.getAsInteger(10, SlotNum)) {
|
|
Diag(ArgLoc, diag::err_hlsl_unsupported_register_number);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!Space.starts_with("space")) {
|
|
Diag(SpaceArgLoc, diag::err_hlsl_expected_space) << Space;
|
|
return;
|
|
}
|
|
StringRef SpaceNumStr = Space.substr(5);
|
|
if (SpaceNumStr.getAsInteger(10, SpaceNum)) {
|
|
Diag(SpaceArgLoc, diag::err_hlsl_expected_space) << Space;
|
|
return;
|
|
}
|
|
|
|
if (!DiagnoseHLSLRegisterAttribute(SemaRef, ArgLoc, TheDecl, RegType,
|
|
SpecifiedSpace))
|
|
return;
|
|
|
|
HLSLResourceBindingAttr *NewAttr =
|
|
HLSLResourceBindingAttr::Create(getASTContext(), Slot, Space, AL);
|
|
if (NewAttr) {
|
|
NewAttr->setBinding(RegType, SlotNum, SpaceNum);
|
|
TheDecl->addAttr(NewAttr);
|
|
}
|
|
}
|
|
|
|
void SemaHLSL::handleParamModifierAttr(Decl *D, const ParsedAttr &AL) {
|
|
HLSLParamModifierAttr *NewAttr = mergeParamModifierAttr(
|
|
D, AL,
|
|
static_cast<HLSLParamModifierAttr::Spelling>(AL.getSemanticSpelling()));
|
|
if (NewAttr)
|
|
D->addAttr(NewAttr);
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// This class implements HLSL availability diagnostics for default
|
|
/// and relaxed mode
|
|
///
|
|
/// The goal of this diagnostic is to emit an error or warning when an
|
|
/// unavailable API is found in code that is reachable from the shader
|
|
/// entry function or from an exported function (when compiling a shader
|
|
/// library).
|
|
///
|
|
/// This is done by traversing the AST of all shader entry point functions
|
|
/// and of all exported functions, and any functions that are referenced
|
|
/// from this AST. In other words, any functions that are reachable from
|
|
/// the entry points.
|
|
class DiagnoseHLSLAvailability : public DynamicRecursiveASTVisitor {
|
|
Sema &SemaRef;
|
|
|
|
// Stack of functions to be scaned
|
|
llvm::SmallVector<const FunctionDecl *, 8> DeclsToScan;
|
|
|
|
// Tracks which environments functions have been scanned in.
|
|
//
|
|
// Maps FunctionDecl to an unsigned number that represents the set of shader
|
|
// environments the function has been scanned for.
|
|
// The llvm::Triple::EnvironmentType enum values for shader stages guaranteed
|
|
// to be numbered from llvm::Triple::Pixel to llvm::Triple::Amplification
|
|
// (verified by static_asserts in Triple.cpp), we can use it to index
|
|
// individual bits in the set, as long as we shift the values to start with 0
|
|
// by subtracting the value of llvm::Triple::Pixel first.
|
|
//
|
|
// The N'th bit in the set will be set if the function has been scanned
|
|
// in shader environment whose llvm::Triple::EnvironmentType integer value
|
|
// equals (llvm::Triple::Pixel + N).
|
|
//
|
|
// For example, if a function has been scanned in compute and pixel stage
|
|
// environment, the value will be 0x21 (100001 binary) because:
|
|
//
|
|
// (int)(llvm::Triple::Pixel - llvm::Triple::Pixel) == 0
|
|
// (int)(llvm::Triple::Compute - llvm::Triple::Pixel) == 5
|
|
//
|
|
// A FunctionDecl is mapped to 0 (or not included in the map) if it has not
|
|
// been scanned in any environment.
|
|
llvm::DenseMap<const FunctionDecl *, unsigned> ScannedDecls;
|
|
|
|
// Do not access these directly, use the get/set methods below to make
|
|
// sure the values are in sync
|
|
llvm::Triple::EnvironmentType CurrentShaderEnvironment;
|
|
unsigned CurrentShaderStageBit;
|
|
|
|
// True if scanning a function that was already scanned in a different
|
|
// shader stage context, and therefore we should not report issues that
|
|
// depend only on shader model version because they would be duplicate.
|
|
bool ReportOnlyShaderStageIssues;
|
|
|
|
// Helper methods for dealing with current stage context / environment
|
|
void SetShaderStageContext(llvm::Triple::EnvironmentType ShaderType) {
|
|
static_assert(sizeof(unsigned) >= 4);
|
|
assert(HLSLShaderAttr::isValidShaderType(ShaderType));
|
|
assert((unsigned)(ShaderType - llvm::Triple::Pixel) < 31 &&
|
|
"ShaderType is too big for this bitmap"); // 31 is reserved for
|
|
// "unknown"
|
|
|
|
unsigned bitmapIndex = ShaderType - llvm::Triple::Pixel;
|
|
CurrentShaderEnvironment = ShaderType;
|
|
CurrentShaderStageBit = (1 << bitmapIndex);
|
|
}
|
|
|
|
void SetUnknownShaderStageContext() {
|
|
CurrentShaderEnvironment = llvm::Triple::UnknownEnvironment;
|
|
CurrentShaderStageBit = (1 << 31);
|
|
}
|
|
|
|
llvm::Triple::EnvironmentType GetCurrentShaderEnvironment() const {
|
|
return CurrentShaderEnvironment;
|
|
}
|
|
|
|
bool InUnknownShaderStageContext() const {
|
|
return CurrentShaderEnvironment == llvm::Triple::UnknownEnvironment;
|
|
}
|
|
|
|
// Helper methods for dealing with shader stage bitmap
|
|
void AddToScannedFunctions(const FunctionDecl *FD) {
|
|
unsigned &ScannedStages = ScannedDecls[FD];
|
|
ScannedStages |= CurrentShaderStageBit;
|
|
}
|
|
|
|
unsigned GetScannedStages(const FunctionDecl *FD) { return ScannedDecls[FD]; }
|
|
|
|
bool WasAlreadyScannedInCurrentStage(const FunctionDecl *FD) {
|
|
return WasAlreadyScannedInCurrentStage(GetScannedStages(FD));
|
|
}
|
|
|
|
bool WasAlreadyScannedInCurrentStage(unsigned ScannerStages) {
|
|
return ScannerStages & CurrentShaderStageBit;
|
|
}
|
|
|
|
static bool NeverBeenScanned(unsigned ScannedStages) {
|
|
return ScannedStages == 0;
|
|
}
|
|
|
|
// Scanning methods
|
|
void HandleFunctionOrMethodRef(FunctionDecl *FD, Expr *RefExpr);
|
|
void CheckDeclAvailability(NamedDecl *D, const AvailabilityAttr *AA,
|
|
SourceRange Range);
|
|
const AvailabilityAttr *FindAvailabilityAttr(const Decl *D);
|
|
bool HasMatchingEnvironmentOrNone(const AvailabilityAttr *AA);
|
|
|
|
public:
|
|
DiagnoseHLSLAvailability(Sema &SemaRef)
|
|
: SemaRef(SemaRef),
|
|
CurrentShaderEnvironment(llvm::Triple::UnknownEnvironment),
|
|
CurrentShaderStageBit(0), ReportOnlyShaderStageIssues(false) {}
|
|
|
|
// AST traversal methods
|
|
void RunOnTranslationUnit(const TranslationUnitDecl *TU);
|
|
void RunOnFunction(const FunctionDecl *FD);
|
|
|
|
bool VisitDeclRefExpr(DeclRefExpr *DRE) override {
|
|
FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(DRE->getDecl());
|
|
if (FD)
|
|
HandleFunctionOrMethodRef(FD, DRE);
|
|
return true;
|
|
}
|
|
|
|
bool VisitMemberExpr(MemberExpr *ME) override {
|
|
FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(ME->getMemberDecl());
|
|
if (FD)
|
|
HandleFunctionOrMethodRef(FD, ME);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void DiagnoseHLSLAvailability::HandleFunctionOrMethodRef(FunctionDecl *FD,
|
|
Expr *RefExpr) {
|
|
assert((isa<DeclRefExpr>(RefExpr) || isa<MemberExpr>(RefExpr)) &&
|
|
"expected DeclRefExpr or MemberExpr");
|
|
|
|
// has a definition -> add to stack to be scanned
|
|
const FunctionDecl *FDWithBody = nullptr;
|
|
if (FD->hasBody(FDWithBody)) {
|
|
if (!WasAlreadyScannedInCurrentStage(FDWithBody))
|
|
DeclsToScan.push_back(FDWithBody);
|
|
return;
|
|
}
|
|
|
|
// no body -> diagnose availability
|
|
const AvailabilityAttr *AA = FindAvailabilityAttr(FD);
|
|
if (AA)
|
|
CheckDeclAvailability(
|
|
FD, AA, SourceRange(RefExpr->getBeginLoc(), RefExpr->getEndLoc()));
|
|
}
|
|
|
|
void DiagnoseHLSLAvailability::RunOnTranslationUnit(
|
|
const TranslationUnitDecl *TU) {
|
|
|
|
// Iterate over all shader entry functions and library exports, and for those
|
|
// that have a body (definiton), run diag scan on each, setting appropriate
|
|
// shader environment context based on whether it is a shader entry function
|
|
// or an exported function. Exported functions can be in namespaces and in
|
|
// export declarations so we need to scan those declaration contexts as well.
|
|
llvm::SmallVector<const DeclContext *, 8> DeclContextsToScan;
|
|
DeclContextsToScan.push_back(TU);
|
|
|
|
while (!DeclContextsToScan.empty()) {
|
|
const DeclContext *DC = DeclContextsToScan.pop_back_val();
|
|
for (auto &D : DC->decls()) {
|
|
// do not scan implicit declaration generated by the implementation
|
|
if (D->isImplicit())
|
|
continue;
|
|
|
|
// for namespace or export declaration add the context to the list to be
|
|
// scanned later
|
|
if (llvm::dyn_cast<NamespaceDecl>(D) || llvm::dyn_cast<ExportDecl>(D)) {
|
|
DeclContextsToScan.push_back(llvm::dyn_cast<DeclContext>(D));
|
|
continue;
|
|
}
|
|
|
|
// skip over other decls or function decls without body
|
|
const FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(D);
|
|
if (!FD || !FD->isThisDeclarationADefinition())
|
|
continue;
|
|
|
|
// shader entry point
|
|
if (HLSLShaderAttr *ShaderAttr = FD->getAttr<HLSLShaderAttr>()) {
|
|
SetShaderStageContext(ShaderAttr->getType());
|
|
RunOnFunction(FD);
|
|
continue;
|
|
}
|
|
// exported library function
|
|
// FIXME: replace this loop with external linkage check once issue #92071
|
|
// is resolved
|
|
bool isExport = FD->isInExportDeclContext();
|
|
if (!isExport) {
|
|
for (const auto *Redecl : FD->redecls()) {
|
|
if (Redecl->isInExportDeclContext()) {
|
|
isExport = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (isExport) {
|
|
SetUnknownShaderStageContext();
|
|
RunOnFunction(FD);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DiagnoseHLSLAvailability::RunOnFunction(const FunctionDecl *FD) {
|
|
assert(DeclsToScan.empty() && "DeclsToScan should be empty");
|
|
DeclsToScan.push_back(FD);
|
|
|
|
while (!DeclsToScan.empty()) {
|
|
// Take one decl from the stack and check it by traversing its AST.
|
|
// For any CallExpr found during the traversal add it's callee to the top of
|
|
// the stack to be processed next. Functions already processed are stored in
|
|
// ScannedDecls.
|
|
const FunctionDecl *FD = DeclsToScan.pop_back_val();
|
|
|
|
// Decl was already scanned
|
|
const unsigned ScannedStages = GetScannedStages(FD);
|
|
if (WasAlreadyScannedInCurrentStage(ScannedStages))
|
|
continue;
|
|
|
|
ReportOnlyShaderStageIssues = !NeverBeenScanned(ScannedStages);
|
|
|
|
AddToScannedFunctions(FD);
|
|
TraverseStmt(FD->getBody());
|
|
}
|
|
}
|
|
|
|
bool DiagnoseHLSLAvailability::HasMatchingEnvironmentOrNone(
|
|
const AvailabilityAttr *AA) {
|
|
IdentifierInfo *IIEnvironment = AA->getEnvironment();
|
|
if (!IIEnvironment)
|
|
return true;
|
|
|
|
llvm::Triple::EnvironmentType CurrentEnv = GetCurrentShaderEnvironment();
|
|
if (CurrentEnv == llvm::Triple::UnknownEnvironment)
|
|
return false;
|
|
|
|
llvm::Triple::EnvironmentType AttrEnv =
|
|
AvailabilityAttr::getEnvironmentType(IIEnvironment->getName());
|
|
|
|
return CurrentEnv == AttrEnv;
|
|
}
|
|
|
|
const AvailabilityAttr *
|
|
DiagnoseHLSLAvailability::FindAvailabilityAttr(const Decl *D) {
|
|
AvailabilityAttr const *PartialMatch = nullptr;
|
|
// Check each AvailabilityAttr to find the one for this platform.
|
|
// For multiple attributes with the same platform try to find one for this
|
|
// environment.
|
|
for (const auto *A : D->attrs()) {
|
|
if (const auto *Avail = dyn_cast<AvailabilityAttr>(A)) {
|
|
StringRef AttrPlatform = Avail->getPlatform()->getName();
|
|
StringRef TargetPlatform =
|
|
SemaRef.getASTContext().getTargetInfo().getPlatformName();
|
|
|
|
// Match the platform name.
|
|
if (AttrPlatform == TargetPlatform) {
|
|
// Find the best matching attribute for this environment
|
|
if (HasMatchingEnvironmentOrNone(Avail))
|
|
return Avail;
|
|
PartialMatch = Avail;
|
|
}
|
|
}
|
|
}
|
|
return PartialMatch;
|
|
}
|
|
|
|
// Check availability against target shader model version and current shader
|
|
// stage and emit diagnostic
|
|
void DiagnoseHLSLAvailability::CheckDeclAvailability(NamedDecl *D,
|
|
const AvailabilityAttr *AA,
|
|
SourceRange Range) {
|
|
|
|
IdentifierInfo *IIEnv = AA->getEnvironment();
|
|
|
|
if (!IIEnv) {
|
|
// The availability attribute does not have environment -> it depends only
|
|
// on shader model version and not on specific the shader stage.
|
|
|
|
// Skip emitting the diagnostics if the diagnostic mode is set to
|
|
// strict (-fhlsl-strict-availability) because all relevant diagnostics
|
|
// were already emitted in the DiagnoseUnguardedAvailability scan
|
|
// (SemaAvailability.cpp).
|
|
if (SemaRef.getLangOpts().HLSLStrictAvailability)
|
|
return;
|
|
|
|
// Do not report shader-stage-independent issues if scanning a function
|
|
// that was already scanned in a different shader stage context (they would
|
|
// be duplicate)
|
|
if (ReportOnlyShaderStageIssues)
|
|
return;
|
|
|
|
} else {
|
|
// The availability attribute has environment -> we need to know
|
|
// the current stage context to property diagnose it.
|
|
if (InUnknownShaderStageContext())
|
|
return;
|
|
}
|
|
|
|
// Check introduced version and if environment matches
|
|
bool EnvironmentMatches = HasMatchingEnvironmentOrNone(AA);
|
|
VersionTuple Introduced = AA->getIntroduced();
|
|
VersionTuple TargetVersion =
|
|
SemaRef.Context.getTargetInfo().getPlatformMinVersion();
|
|
|
|
if (TargetVersion >= Introduced && EnvironmentMatches)
|
|
return;
|
|
|
|
// Emit diagnostic message
|
|
const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
|
|
llvm::StringRef PlatformName(
|
|
AvailabilityAttr::getPrettyPlatformName(TI.getPlatformName()));
|
|
|
|
llvm::StringRef CurrentEnvStr =
|
|
llvm::Triple::getEnvironmentTypeName(GetCurrentShaderEnvironment());
|
|
|
|
llvm::StringRef AttrEnvStr =
|
|
AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
|
|
bool UseEnvironment = !AttrEnvStr.empty();
|
|
|
|
if (EnvironmentMatches) {
|
|
SemaRef.Diag(Range.getBegin(), diag::warn_hlsl_availability)
|
|
<< Range << D << PlatformName << Introduced.getAsString()
|
|
<< UseEnvironment << CurrentEnvStr;
|
|
} else {
|
|
SemaRef.Diag(Range.getBegin(), diag::warn_hlsl_availability_unavailable)
|
|
<< Range << D;
|
|
}
|
|
|
|
SemaRef.Diag(D->getLocation(), diag::note_partial_availability_specified_here)
|
|
<< D << PlatformName << Introduced.getAsString()
|
|
<< SemaRef.Context.getTargetInfo().getPlatformMinVersion().getAsString()
|
|
<< UseEnvironment << AttrEnvStr << CurrentEnvStr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SemaHLSL::ActOnEndOfTranslationUnit(TranslationUnitDecl *TU) {
|
|
// process default CBuffer - create buffer layout struct and invoke codegenCGH
|
|
if (!DefaultCBufferDecls.empty()) {
|
|
HLSLBufferDecl *DefaultCBuffer = HLSLBufferDecl::CreateDefaultCBuffer(
|
|
SemaRef.getASTContext(), SemaRef.getCurLexicalContext(),
|
|
DefaultCBufferDecls);
|
|
SemaRef.getCurLexicalContext()->addDecl(DefaultCBuffer);
|
|
createHostLayoutStructForBuffer(SemaRef, DefaultCBuffer);
|
|
|
|
// Set HasValidPackoffset if any of the decls has a register(c#) annotation;
|
|
for (const Decl *VD : DefaultCBufferDecls) {
|
|
const HLSLResourceBindingAttr *RBA =
|
|
VD->getAttr<HLSLResourceBindingAttr>();
|
|
if (RBA &&
|
|
RBA->getRegisterType() == HLSLResourceBindingAttr::RegisterType::C) {
|
|
DefaultCBuffer->setHasValidPackoffset(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DeclGroupRef DG(DefaultCBuffer);
|
|
SemaRef.Consumer.HandleTopLevelDecl(DG);
|
|
}
|
|
diagnoseAvailabilityViolations(TU);
|
|
}
|
|
|
|
void SemaHLSL::diagnoseAvailabilityViolations(TranslationUnitDecl *TU) {
|
|
// Skip running the diagnostics scan if the diagnostic mode is
|
|
// strict (-fhlsl-strict-availability) and the target shader stage is known
|
|
// because all relevant diagnostics were already emitted in the
|
|
// DiagnoseUnguardedAvailability scan (SemaAvailability.cpp).
|
|
const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
|
|
if (SemaRef.getLangOpts().HLSLStrictAvailability &&
|
|
TI.getTriple().getEnvironment() != llvm::Triple::EnvironmentType::Library)
|
|
return;
|
|
|
|
DiagnoseHLSLAvailability(SemaRef).RunOnTranslationUnit(TU);
|
|
}
|
|
|
|
// Helper function for CheckHLSLBuiltinFunctionCall
|
|
static bool CheckVectorElementCallArgs(Sema *S, CallExpr *TheCall) {
|
|
assert(TheCall->getNumArgs() > 1);
|
|
ExprResult A = TheCall->getArg(0);
|
|
|
|
QualType ArgTyA = A.get()->getType();
|
|
|
|
auto *VecTyA = ArgTyA->getAs<VectorType>();
|
|
SourceLocation BuiltinLoc = TheCall->getBeginLoc();
|
|
|
|
bool AllBArgAreVectors = true;
|
|
for (unsigned i = 1; i < TheCall->getNumArgs(); ++i) {
|
|
ExprResult B = TheCall->getArg(i);
|
|
QualType ArgTyB = B.get()->getType();
|
|
auto *VecTyB = ArgTyB->getAs<VectorType>();
|
|
if (VecTyB == nullptr)
|
|
AllBArgAreVectors &= false;
|
|
if (VecTyA && VecTyB == nullptr) {
|
|
// Note: if we get here 'B' is scalar which
|
|
// requires a VectorSplat on ArgN
|
|
S->Diag(BuiltinLoc, diag::err_vec_builtin_non_vector)
|
|
<< TheCall->getDirectCallee() << /*useAllTerminology*/ true
|
|
<< SourceRange(A.get()->getBeginLoc(), B.get()->getEndLoc());
|
|
return true;
|
|
}
|
|
if (VecTyA && VecTyB) {
|
|
bool retValue = false;
|
|
if (!S->Context.hasSameUnqualifiedType(VecTyA->getElementType(),
|
|
VecTyB->getElementType())) {
|
|
// Note: type promotion is intended to be handeled via the intrinsics
|
|
// and not the builtin itself.
|
|
S->Diag(TheCall->getBeginLoc(),
|
|
diag::err_vec_builtin_incompatible_vector)
|
|
<< TheCall->getDirectCallee() << /*useAllTerminology*/ true
|
|
<< SourceRange(A.get()->getBeginLoc(), B.get()->getEndLoc());
|
|
retValue = true;
|
|
}
|
|
if (VecTyA->getNumElements() != VecTyB->getNumElements()) {
|
|
// You should only be hitting this case if you are calling the builtin
|
|
// directly. HLSL intrinsics should avoid this case via a
|
|
// HLSLVectorTruncation.
|
|
S->Diag(BuiltinLoc, diag::err_vec_builtin_incompatible_vector)
|
|
<< TheCall->getDirectCallee() << /*useAllTerminology*/ true
|
|
<< SourceRange(A.get()->getBeginLoc(), B.get()->getEndLoc());
|
|
retValue = true;
|
|
}
|
|
if (retValue)
|
|
return retValue;
|
|
}
|
|
}
|
|
|
|
if (VecTyA == nullptr && AllBArgAreVectors) {
|
|
// Note: if we get here 'A' is a scalar which
|
|
// requires a VectorSplat on Arg0
|
|
S->Diag(BuiltinLoc, diag::err_vec_builtin_non_vector)
|
|
<< TheCall->getDirectCallee() << /*useAllTerminology*/ true
|
|
<< SourceRange(A.get()->getBeginLoc(), A.get()->getEndLoc());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckAllArgsHaveSameType(Sema *S, CallExpr *TheCall) {
|
|
assert(TheCall->getNumArgs() > 1);
|
|
QualType ArgTy0 = TheCall->getArg(0)->getType();
|
|
|
|
for (unsigned I = 1, N = TheCall->getNumArgs(); I < N; ++I) {
|
|
if (!S->getASTContext().hasSameUnqualifiedType(
|
|
ArgTy0, TheCall->getArg(I)->getType())) {
|
|
S->Diag(TheCall->getBeginLoc(), diag::err_vec_builtin_incompatible_vector)
|
|
<< TheCall->getDirectCallee() << /*useAllTerminology*/ true
|
|
<< SourceRange(TheCall->getArg(0)->getBeginLoc(),
|
|
TheCall->getArg(N - 1)->getEndLoc());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckArgTypeMatches(Sema *S, Expr *Arg, QualType ExpectedType) {
|
|
QualType ArgType = Arg->getType();
|
|
if (!S->getASTContext().hasSameUnqualifiedType(ArgType, ExpectedType)) {
|
|
S->Diag(Arg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
|
|
<< ArgType << ExpectedType << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckArgTypeIsCorrect(
|
|
Sema *S, Expr *Arg, QualType ExpectedType,
|
|
llvm::function_ref<bool(clang::QualType PassedType)> Check) {
|
|
QualType PassedType = Arg->getType();
|
|
if (Check(PassedType)) {
|
|
if (auto *VecTyA = PassedType->getAs<VectorType>())
|
|
ExpectedType = S->Context.getVectorType(
|
|
ExpectedType, VecTyA->getNumElements(), VecTyA->getVectorKind());
|
|
S->Diag(Arg->getBeginLoc(), diag::err_typecheck_convert_incompatible)
|
|
<< PassedType << ExpectedType << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckAllArgTypesAreCorrect(
|
|
Sema *S, CallExpr *TheCall, QualType ExpectedType,
|
|
llvm::function_ref<bool(clang::QualType PassedType)> Check) {
|
|
for (unsigned i = 0; i < TheCall->getNumArgs(); ++i) {
|
|
Expr *Arg = TheCall->getArg(i);
|
|
if (CheckArgTypeIsCorrect(S, Arg, ExpectedType, Check)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckAllArgsHaveFloatRepresentation(Sema *S, CallExpr *TheCall) {
|
|
auto checkAllFloatTypes = [](clang::QualType PassedType) -> bool {
|
|
return !PassedType->hasFloatingRepresentation();
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.FloatTy,
|
|
checkAllFloatTypes);
|
|
}
|
|
|
|
static bool CheckUnsignedIntRepresentations(Sema *S, CallExpr *TheCall) {
|
|
auto checkUnsignedInteger = [](clang::QualType PassedType) -> bool {
|
|
clang::QualType BaseType =
|
|
PassedType->isVectorType()
|
|
? PassedType->getAs<clang::VectorType>()->getElementType()
|
|
: PassedType;
|
|
return !BaseType->isUnsignedIntegerType();
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.UnsignedIntTy,
|
|
checkUnsignedInteger);
|
|
}
|
|
|
|
static bool CheckFloatOrHalfRepresentations(Sema *S, CallExpr *TheCall) {
|
|
auto checkFloatorHalf = [](clang::QualType PassedType) -> bool {
|
|
clang::QualType BaseType =
|
|
PassedType->isVectorType()
|
|
? PassedType->getAs<clang::VectorType>()->getElementType()
|
|
: PassedType;
|
|
return !BaseType->isHalfType() && !BaseType->isFloat32Type();
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.FloatTy,
|
|
checkFloatorHalf);
|
|
}
|
|
|
|
static bool CheckModifiableLValue(Sema *S, CallExpr *TheCall,
|
|
unsigned ArgIndex) {
|
|
auto *Arg = TheCall->getArg(ArgIndex);
|
|
SourceLocation OrigLoc = Arg->getExprLoc();
|
|
if (Arg->IgnoreCasts()->isModifiableLvalue(S->Context, &OrigLoc) ==
|
|
Expr::MLV_Valid)
|
|
return false;
|
|
S->Diag(OrigLoc, diag::error_hlsl_inout_lvalue) << Arg << 0;
|
|
return true;
|
|
}
|
|
|
|
static bool CheckNoDoubleVectors(Sema *S, CallExpr *TheCall) {
|
|
auto checkDoubleVector = [](clang::QualType PassedType) -> bool {
|
|
if (const auto *VecTy = PassedType->getAs<VectorType>())
|
|
return VecTy->getElementType()->isDoubleType();
|
|
return false;
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.FloatTy,
|
|
checkDoubleVector);
|
|
}
|
|
static bool CheckFloatingOrIntRepresentation(Sema *S, CallExpr *TheCall) {
|
|
auto checkAllSignedTypes = [](clang::QualType PassedType) -> bool {
|
|
return !PassedType->hasIntegerRepresentation() &&
|
|
!PassedType->hasFloatingRepresentation();
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.IntTy,
|
|
checkAllSignedTypes);
|
|
}
|
|
|
|
static bool CheckUnsignedIntRepresentation(Sema *S, CallExpr *TheCall) {
|
|
auto checkAllUnsignedTypes = [](clang::QualType PassedType) -> bool {
|
|
return !PassedType->hasUnsignedIntegerRepresentation();
|
|
};
|
|
return CheckAllArgTypesAreCorrect(S, TheCall, S->Context.UnsignedIntTy,
|
|
checkAllUnsignedTypes);
|
|
}
|
|
|
|
static void SetElementTypeAsReturnType(Sema *S, CallExpr *TheCall,
|
|
QualType ReturnType) {
|
|
auto *VecTyA = TheCall->getArg(0)->getType()->getAs<VectorType>();
|
|
if (VecTyA)
|
|
ReturnType = S->Context.getVectorType(ReturnType, VecTyA->getNumElements(),
|
|
VectorKind::Generic);
|
|
TheCall->setType(ReturnType);
|
|
}
|
|
|
|
static bool CheckScalarOrVector(Sema *S, CallExpr *TheCall, QualType Scalar,
|
|
unsigned ArgIndex) {
|
|
assert(TheCall->getNumArgs() >= ArgIndex);
|
|
QualType ArgType = TheCall->getArg(ArgIndex)->getType();
|
|
auto *VTy = ArgType->getAs<VectorType>();
|
|
// not the scalar or vector<scalar>
|
|
if (!(S->Context.hasSameUnqualifiedType(ArgType, Scalar) ||
|
|
(VTy &&
|
|
S->Context.hasSameUnqualifiedType(VTy->getElementType(), Scalar)))) {
|
|
S->Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_typecheck_expect_scalar_or_vector)
|
|
<< ArgType << Scalar;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckAnyScalarOrVector(Sema *S, CallExpr *TheCall,
|
|
unsigned ArgIndex) {
|
|
assert(TheCall->getNumArgs() >= ArgIndex);
|
|
QualType ArgType = TheCall->getArg(ArgIndex)->getType();
|
|
auto *VTy = ArgType->getAs<VectorType>();
|
|
// not the scalar or vector<scalar>
|
|
if (!(ArgType->isScalarType() ||
|
|
(VTy && VTy->getElementType()->isScalarType()))) {
|
|
S->Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_typecheck_expect_any_scalar_or_vector)
|
|
<< ArgType << 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckWaveActive(Sema *S, CallExpr *TheCall) {
|
|
QualType BoolType = S->getASTContext().BoolTy;
|
|
assert(TheCall->getNumArgs() >= 1);
|
|
QualType ArgType = TheCall->getArg(0)->getType();
|
|
auto *VTy = ArgType->getAs<VectorType>();
|
|
// is the bool or vector<bool>
|
|
if (S->Context.hasSameUnqualifiedType(ArgType, BoolType) ||
|
|
(VTy &&
|
|
S->Context.hasSameUnqualifiedType(VTy->getElementType(), BoolType))) {
|
|
S->Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_typecheck_expect_any_scalar_or_vector)
|
|
<< ArgType << 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckBoolSelect(Sema *S, CallExpr *TheCall) {
|
|
assert(TheCall->getNumArgs() == 3);
|
|
Expr *Arg1 = TheCall->getArg(1);
|
|
Expr *Arg2 = TheCall->getArg(2);
|
|
if (!S->Context.hasSameUnqualifiedType(Arg1->getType(), Arg2->getType())) {
|
|
S->Diag(TheCall->getBeginLoc(),
|
|
diag::err_typecheck_call_different_arg_types)
|
|
<< Arg1->getType() << Arg2->getType() << Arg1->getSourceRange()
|
|
<< Arg2->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
TheCall->setType(Arg1->getType());
|
|
return false;
|
|
}
|
|
|
|
static bool CheckVectorSelect(Sema *S, CallExpr *TheCall) {
|
|
assert(TheCall->getNumArgs() == 3);
|
|
Expr *Arg1 = TheCall->getArg(1);
|
|
QualType Arg1Ty = Arg1->getType();
|
|
Expr *Arg2 = TheCall->getArg(2);
|
|
QualType Arg2Ty = Arg2->getType();
|
|
|
|
QualType Arg1ScalarTy = Arg1Ty;
|
|
if (auto VTy = Arg1ScalarTy->getAs<VectorType>())
|
|
Arg1ScalarTy = VTy->getElementType();
|
|
|
|
QualType Arg2ScalarTy = Arg2Ty;
|
|
if (auto VTy = Arg2ScalarTy->getAs<VectorType>())
|
|
Arg2ScalarTy = VTy->getElementType();
|
|
|
|
if (!S->Context.hasSameUnqualifiedType(Arg1ScalarTy, Arg2ScalarTy))
|
|
S->Diag(Arg1->getBeginLoc(), diag::err_hlsl_builtin_scalar_vector_mismatch)
|
|
<< /* second and third */ 1 << TheCall->getCallee() << Arg1Ty << Arg2Ty;
|
|
|
|
QualType Arg0Ty = TheCall->getArg(0)->getType();
|
|
unsigned Arg0Length = Arg0Ty->getAs<VectorType>()->getNumElements();
|
|
unsigned Arg1Length = Arg1Ty->isVectorType()
|
|
? Arg1Ty->getAs<VectorType>()->getNumElements()
|
|
: 0;
|
|
unsigned Arg2Length = Arg2Ty->isVectorType()
|
|
? Arg2Ty->getAs<VectorType>()->getNumElements()
|
|
: 0;
|
|
if (Arg1Length > 0 && Arg0Length != Arg1Length) {
|
|
S->Diag(TheCall->getBeginLoc(),
|
|
diag::err_typecheck_vector_lengths_not_equal)
|
|
<< Arg0Ty << Arg1Ty << TheCall->getArg(0)->getSourceRange()
|
|
<< Arg1->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
if (Arg2Length > 0 && Arg0Length != Arg2Length) {
|
|
S->Diag(TheCall->getBeginLoc(),
|
|
diag::err_typecheck_vector_lengths_not_equal)
|
|
<< Arg0Ty << Arg2Ty << TheCall->getArg(0)->getSourceRange()
|
|
<< Arg2->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
TheCall->setType(
|
|
S->getASTContext().getExtVectorType(Arg1ScalarTy, Arg0Length));
|
|
return false;
|
|
}
|
|
|
|
static bool CheckResourceHandle(
|
|
Sema *S, CallExpr *TheCall, unsigned ArgIndex,
|
|
llvm::function_ref<bool(const HLSLAttributedResourceType *ResType)> Check =
|
|
nullptr) {
|
|
assert(TheCall->getNumArgs() >= ArgIndex);
|
|
QualType ArgType = TheCall->getArg(ArgIndex)->getType();
|
|
const HLSLAttributedResourceType *ResTy =
|
|
ArgType.getTypePtr()->getAs<HLSLAttributedResourceType>();
|
|
if (!ResTy) {
|
|
S->Diag(TheCall->getArg(ArgIndex)->getBeginLoc(),
|
|
diag::err_typecheck_expect_hlsl_resource)
|
|
<< ArgType;
|
|
return true;
|
|
}
|
|
if (Check && Check(ResTy)) {
|
|
S->Diag(TheCall->getArg(ArgIndex)->getExprLoc(),
|
|
diag::err_invalid_hlsl_resource_type)
|
|
<< ArgType;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Note: returning true in this case results in CheckBuiltinFunctionCall
|
|
// returning an ExprError
|
|
bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
|
|
switch (BuiltinID) {
|
|
case Builtin::BI__builtin_hlsl_adduint64: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (CheckUnsignedIntRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
|
|
// CheckVectorElementCallArgs(...) guarantees both args are the same type.
|
|
assert(TheCall->getArg(0)->getType() == TheCall->getArg(1)->getType() &&
|
|
"Both args must be of the same type");
|
|
|
|
// ensure both args are vectors
|
|
auto *VTy = TheCall->getArg(0)->getType()->getAs<VectorType>();
|
|
if (!VTy) {
|
|
SemaRef.Diag(TheCall->getBeginLoc(), diag::err_vec_builtin_non_vector)
|
|
<< TheCall->getDirectCallee() << /*all*/ 1;
|
|
return true;
|
|
}
|
|
|
|
// ensure arg integers are 32-bits
|
|
uint64_t ElementBitCount = getASTContext()
|
|
.getTypeSizeInChars(VTy->getElementType())
|
|
.getQuantity() *
|
|
8;
|
|
if (ElementBitCount != 32) {
|
|
SemaRef.Diag(TheCall->getBeginLoc(),
|
|
diag::err_integer_incorrect_bit_count)
|
|
<< 32 << ElementBitCount;
|
|
return true;
|
|
}
|
|
|
|
// ensure both args are vectors of total bit size of a multiple of 64
|
|
int NumElementsArg = VTy->getNumElements();
|
|
if (NumElementsArg != 2 && NumElementsArg != 4) {
|
|
SemaRef.Diag(TheCall->getBeginLoc(), diag::err_vector_incorrect_bit_count)
|
|
<< 1 /*a multiple of*/ << 64 << NumElementsArg * ElementBitCount;
|
|
return true;
|
|
}
|
|
|
|
ExprResult A = TheCall->getArg(0);
|
|
QualType ArgTyA = A.get()->getType();
|
|
// return type is the same as the input type
|
|
TheCall->setType(ArgTyA);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_getpointer: {
|
|
if (SemaRef.checkArgCount(TheCall, 2) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1),
|
|
SemaRef.getASTContext().UnsignedIntTy))
|
|
return true;
|
|
|
|
auto *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
QualType ContainedTy = ResourceTy->getContainedType();
|
|
// TODO: Map to an hlsl_device address space.
|
|
TheCall->setType(getASTContext().getPointerType(ContainedTy));
|
|
TheCall->setValueKind(VK_LValue);
|
|
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_and:
|
|
case Builtin::BI__builtin_hlsl_or: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (CheckScalarOrVector(&SemaRef, TheCall, getASTContext().BoolTy, 0))
|
|
return true;
|
|
|
|
ExprResult A = TheCall->getArg(0);
|
|
QualType ArgTyA = A.get()->getType();
|
|
// return type is the same as the input type
|
|
TheCall->setType(ArgTyA);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_all:
|
|
case Builtin::BI__builtin_hlsl_any: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_asdouble: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckUnsignedIntRepresentation(&SemaRef, TheCall))
|
|
return true;
|
|
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().DoubleTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_clamp: {
|
|
if (SemaRef.checkArgCount(TheCall, 3))
|
|
return true;
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0) ||
|
|
CheckAllArgsHaveSameType(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(
|
|
TheCall, /*ArgTyRestr=*/
|
|
TheCall->getArg(0)->getType()->hasFloatingRepresentation()
|
|
? Sema::EltwiseBuiltinArgTyRestriction::FloatTy
|
|
: Sema::EltwiseBuiltinArgTyRestriction::None))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_cross: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
// ensure both args have 3 elements
|
|
int NumElementsArg1 =
|
|
TheCall->getArg(0)->getType()->castAs<VectorType>()->getNumElements();
|
|
int NumElementsArg2 =
|
|
TheCall->getArg(1)->getType()->castAs<VectorType>()->getNumElements();
|
|
|
|
if (NumElementsArg1 != 3) {
|
|
int LessOrMore = NumElementsArg1 > 3 ? 1 : 0;
|
|
SemaRef.Diag(TheCall->getBeginLoc(),
|
|
diag::err_vector_incorrect_num_elements)
|
|
<< LessOrMore << 3 << NumElementsArg1 << /*operand*/ 1;
|
|
return true;
|
|
}
|
|
if (NumElementsArg2 != 3) {
|
|
int LessOrMore = NumElementsArg2 > 3 ? 1 : 0;
|
|
|
|
SemaRef.Diag(TheCall->getBeginLoc(),
|
|
diag::err_vector_incorrect_num_elements)
|
|
<< LessOrMore << 3 << NumElementsArg2 << /*operand*/ 1;
|
|
return true;
|
|
}
|
|
|
|
ExprResult A = TheCall->getArg(0);
|
|
QualType ArgTyA = A.get()->getType();
|
|
// return type is the same as the input type
|
|
TheCall->setType(ArgTyA);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_dot: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.BuiltinVectorToScalarMath(TheCall))
|
|
return true;
|
|
if (CheckNoDoubleVectors(&SemaRef, TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_firstbithigh:
|
|
case Builtin::BI__builtin_hlsl_elementwise_firstbitlow: {
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
|
|
const Expr *Arg = TheCall->getArg(0);
|
|
QualType ArgTy = Arg->getType();
|
|
QualType EltTy = ArgTy;
|
|
|
|
QualType ResTy = SemaRef.Context.UnsignedIntTy;
|
|
|
|
if (auto *VecTy = EltTy->getAs<VectorType>()) {
|
|
EltTy = VecTy->getElementType();
|
|
ResTy = SemaRef.Context.getVectorType(ResTy, VecTy->getNumElements(),
|
|
VecTy->getVectorKind());
|
|
}
|
|
|
|
if (!EltTy->isIntegerType()) {
|
|
Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_type)
|
|
<< 1 << /* scalar or vector of */ 5 << /* integer ty */ 1
|
|
<< /* no fp */ 0 << ArgTy;
|
|
return true;
|
|
}
|
|
|
|
TheCall->setType(ResTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_select: {
|
|
if (SemaRef.checkArgCount(TheCall, 3))
|
|
return true;
|
|
if (CheckScalarOrVector(&SemaRef, TheCall, getASTContext().BoolTy, 0))
|
|
return true;
|
|
QualType ArgTy = TheCall->getArg(0)->getType();
|
|
if (ArgTy->isBooleanType() && CheckBoolSelect(&SemaRef, TheCall))
|
|
return true;
|
|
auto *VTy = ArgTy->getAs<VectorType>();
|
|
if (VTy && VTy->getElementType()->isBooleanType() &&
|
|
CheckVectorSelect(&SemaRef, TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_saturate:
|
|
case Builtin::BI__builtin_hlsl_elementwise_rcp: {
|
|
if (CheckAllArgsHaveFloatRepresentation(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_degrees:
|
|
case Builtin::BI__builtin_hlsl_elementwise_radians:
|
|
case Builtin::BI__builtin_hlsl_elementwise_rsqrt:
|
|
case Builtin::BI__builtin_hlsl_elementwise_frac: {
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_isinf: {
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().BoolTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_lerp: {
|
|
if (SemaRef.checkArgCount(TheCall, 3))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(TheCall))
|
|
return true;
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_mad: {
|
|
if (SemaRef.checkArgCount(TheCall, 3))
|
|
return true;
|
|
if (CheckVectorElementCallArgs(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(
|
|
TheCall, /*ArgTyRestr=*/
|
|
TheCall->getArg(0)->getType()->hasFloatingRepresentation()
|
|
? Sema::EltwiseBuiltinArgTyRestriction::FloatTy
|
|
: Sema::EltwiseBuiltinArgTyRestriction::None))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_normalize: {
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
ExprResult A = TheCall->getArg(0);
|
|
QualType ArgTyA = A.get()->getType();
|
|
// return type is the same as the input type
|
|
TheCall->setType(ArgTyA);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_sign: {
|
|
if (CheckFloatingOrIntRepresentation(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().IntTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_step: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
|
|
ExprResult A = TheCall->getArg(0);
|
|
QualType ArgTyA = A.get()->getType();
|
|
// return type is the same as the input type
|
|
TheCall->setType(ArgTyA);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_active_max:
|
|
case Builtin::BI__builtin_hlsl_wave_active_sum: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
// Ensure input expr type is a scalar/vector and the same as the return type
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
if (CheckWaveActive(&SemaRef, TheCall))
|
|
return true;
|
|
ExprResult Expr = TheCall->getArg(0);
|
|
QualType ArgTyExpr = Expr.get()->getType();
|
|
TheCall->setType(ArgTyExpr);
|
|
break;
|
|
}
|
|
// Note these are llvm builtins that we want to catch invalid intrinsic
|
|
// generation. Normal handling of these builitns will occur elsewhere.
|
|
case Builtin::BI__builtin_elementwise_bitreverse: {
|
|
if (CheckUnsignedIntRepresentation(&SemaRef, TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_read_lane_at: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
|
|
// Ensure index parameter type can be interpreted as a uint
|
|
ExprResult Index = TheCall->getArg(1);
|
|
QualType ArgTyIndex = Index.get()->getType();
|
|
if (!ArgTyIndex->isIntegerType()) {
|
|
SemaRef.Diag(TheCall->getArg(1)->getBeginLoc(),
|
|
diag::err_typecheck_convert_incompatible)
|
|
<< ArgTyIndex << SemaRef.Context.UnsignedIntTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
|
|
// Ensure input expr type is a scalar/vector and the same as the return type
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
|
|
ExprResult Expr = TheCall->getArg(0);
|
|
QualType ArgTyExpr = Expr.get()->getType();
|
|
TheCall->setType(ArgTyExpr);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_get_lane_index: {
|
|
if (SemaRef.checkArgCount(TheCall, 0))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_splitdouble: {
|
|
if (SemaRef.checkArgCount(TheCall, 3))
|
|
return true;
|
|
|
|
if (CheckScalarOrVector(&SemaRef, TheCall, SemaRef.Context.DoubleTy, 0) ||
|
|
CheckScalarOrVector(&SemaRef, TheCall, SemaRef.Context.UnsignedIntTy,
|
|
1) ||
|
|
CheckScalarOrVector(&SemaRef, TheCall, SemaRef.Context.UnsignedIntTy,
|
|
2))
|
|
return true;
|
|
|
|
if (CheckModifiableLValue(&SemaRef, TheCall, 1) ||
|
|
CheckModifiableLValue(&SemaRef, TheCall, 2))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_clip: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
if (CheckScalarOrVector(&SemaRef, TheCall, SemaRef.Context.FloatTy, 0))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_elementwise_acos:
|
|
case Builtin::BI__builtin_elementwise_asin:
|
|
case Builtin::BI__builtin_elementwise_atan:
|
|
case Builtin::BI__builtin_elementwise_atan2:
|
|
case Builtin::BI__builtin_elementwise_ceil:
|
|
case Builtin::BI__builtin_elementwise_cos:
|
|
case Builtin::BI__builtin_elementwise_cosh:
|
|
case Builtin::BI__builtin_elementwise_exp:
|
|
case Builtin::BI__builtin_elementwise_exp2:
|
|
case Builtin::BI__builtin_elementwise_exp10:
|
|
case Builtin::BI__builtin_elementwise_floor:
|
|
case Builtin::BI__builtin_elementwise_fmod:
|
|
case Builtin::BI__builtin_elementwise_log:
|
|
case Builtin::BI__builtin_elementwise_log2:
|
|
case Builtin::BI__builtin_elementwise_log10:
|
|
case Builtin::BI__builtin_elementwise_pow:
|
|
case Builtin::BI__builtin_elementwise_roundeven:
|
|
case Builtin::BI__builtin_elementwise_sin:
|
|
case Builtin::BI__builtin_elementwise_sinh:
|
|
case Builtin::BI__builtin_elementwise_sqrt:
|
|
case Builtin::BI__builtin_elementwise_tan:
|
|
case Builtin::BI__builtin_elementwise_tanh:
|
|
case Builtin::BI__builtin_elementwise_trunc: {
|
|
if (CheckFloatOrHalfRepresentations(&SemaRef, TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_buffer_update_counter: {
|
|
auto checkResTy = [](const HLSLAttributedResourceType *ResTy) -> bool {
|
|
return !(ResTy->getAttrs().ResourceClass == ResourceClass::UAV &&
|
|
ResTy->getAttrs().RawBuffer && ResTy->hasContainedType());
|
|
};
|
|
if (SemaRef.checkArgCount(TheCall, 2) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0, checkResTy) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1),
|
|
SemaRef.getASTContext().IntTy))
|
|
return true;
|
|
Expr *OffsetExpr = TheCall->getArg(1);
|
|
std::optional<llvm::APSInt> Offset =
|
|
OffsetExpr->getIntegerConstantExpr(SemaRef.getASTContext());
|
|
if (!Offset.has_value() || std::abs(Offset->getExtValue()) != 1) {
|
|
SemaRef.Diag(TheCall->getArg(1)->getBeginLoc(),
|
|
diag::err_hlsl_expect_arg_const_int_one_or_neg_one)
|
|
<< 1;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void BuildFlattenedTypeList(QualType BaseTy,
|
|
llvm::SmallVectorImpl<QualType> &List) {
|
|
llvm::SmallVector<QualType, 16> WorkList;
|
|
WorkList.push_back(BaseTy);
|
|
while (!WorkList.empty()) {
|
|
QualType T = WorkList.pop_back_val();
|
|
T = T.getCanonicalType().getUnqualifiedType();
|
|
assert(!isa<MatrixType>(T) && "Matrix types not yet supported in HLSL");
|
|
if (const auto *AT = dyn_cast<ConstantArrayType>(T)) {
|
|
llvm::SmallVector<QualType, 16> ElementFields;
|
|
// Generally I've avoided recursion in this algorithm, but arrays of
|
|
// structs could be time-consuming to flatten and churn through on the
|
|
// work list. Hopefully nesting arrays of structs containing arrays
|
|
// of structs too many levels deep is unlikely.
|
|
BuildFlattenedTypeList(AT->getElementType(), ElementFields);
|
|
// Repeat the element's field list n times.
|
|
for (uint64_t Ct = 0; Ct < AT->getZExtSize(); ++Ct)
|
|
List.insert(List.end(), ElementFields.begin(), ElementFields.end());
|
|
continue;
|
|
}
|
|
// Vectors can only have element types that are builtin types, so this can
|
|
// add directly to the list instead of to the WorkList.
|
|
if (const auto *VT = dyn_cast<VectorType>(T)) {
|
|
List.insert(List.end(), VT->getNumElements(), VT->getElementType());
|
|
continue;
|
|
}
|
|
if (const auto *RT = dyn_cast<RecordType>(T)) {
|
|
const CXXRecordDecl *RD = RT->getAsCXXRecordDecl();
|
|
assert(RD && "HLSL record types should all be CXXRecordDecls!");
|
|
|
|
if (RD->isStandardLayout())
|
|
RD = RD->getStandardLayoutBaseWithFields();
|
|
|
|
// For types that we shouldn't decompose (unions and non-aggregates), just
|
|
// add the type itself to the list.
|
|
if (RD->isUnion() || !RD->isAggregate()) {
|
|
List.push_back(T);
|
|
continue;
|
|
}
|
|
|
|
llvm::SmallVector<QualType, 16> FieldTypes;
|
|
for (const auto *FD : RD->fields())
|
|
FieldTypes.push_back(FD->getType());
|
|
// Reverse the newly added sub-range.
|
|
std::reverse(FieldTypes.begin(), FieldTypes.end());
|
|
WorkList.insert(WorkList.end(), FieldTypes.begin(), FieldTypes.end());
|
|
|
|
// If this wasn't a standard layout type we may also have some base
|
|
// classes to deal with.
|
|
if (!RD->isStandardLayout()) {
|
|
FieldTypes.clear();
|
|
for (const auto &Base : RD->bases())
|
|
FieldTypes.push_back(Base.getType());
|
|
std::reverse(FieldTypes.begin(), FieldTypes.end());
|
|
WorkList.insert(WorkList.end(), FieldTypes.begin(), FieldTypes.end());
|
|
}
|
|
continue;
|
|
}
|
|
List.push_back(T);
|
|
}
|
|
}
|
|
|
|
bool SemaHLSL::IsTypedResourceElementCompatible(clang::QualType QT) {
|
|
// null and array types are not allowed.
|
|
if (QT.isNull() || QT->isArrayType())
|
|
return false;
|
|
|
|
// UDT types are not allowed
|
|
if (QT->isRecordType())
|
|
return false;
|
|
|
|
if (QT->isBooleanType() || QT->isEnumeralType())
|
|
return false;
|
|
|
|
// the only other valid builtin types are scalars or vectors
|
|
if (QT->isArithmeticType()) {
|
|
if (SemaRef.Context.getTypeSize(QT) / 8 > 16)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if (const VectorType *VT = QT->getAs<VectorType>()) {
|
|
int ArraySize = VT->getNumElements();
|
|
|
|
if (ArraySize > 4)
|
|
return false;
|
|
|
|
QualType ElTy = VT->getElementType();
|
|
if (ElTy->isBooleanType())
|
|
return false;
|
|
|
|
if (SemaRef.Context.getTypeSize(QT) / 8 > 16)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SemaHLSL::IsScalarizedLayoutCompatible(QualType T1, QualType T2) const {
|
|
if (T1.isNull() || T2.isNull())
|
|
return false;
|
|
|
|
T1 = T1.getCanonicalType().getUnqualifiedType();
|
|
T2 = T2.getCanonicalType().getUnqualifiedType();
|
|
|
|
// If both types are the same canonical type, they're obviously compatible.
|
|
if (SemaRef.getASTContext().hasSameType(T1, T2))
|
|
return true;
|
|
|
|
llvm::SmallVector<QualType, 16> T1Types;
|
|
BuildFlattenedTypeList(T1, T1Types);
|
|
llvm::SmallVector<QualType, 16> T2Types;
|
|
BuildFlattenedTypeList(T2, T2Types);
|
|
|
|
// Check the flattened type list
|
|
return llvm::equal(T1Types, T2Types,
|
|
[this](QualType LHS, QualType RHS) -> bool {
|
|
return SemaRef.IsLayoutCompatible(LHS, RHS);
|
|
});
|
|
}
|
|
|
|
bool SemaHLSL::CheckCompatibleParameterABI(FunctionDecl *New,
|
|
FunctionDecl *Old) {
|
|
if (New->getNumParams() != Old->getNumParams())
|
|
return true;
|
|
|
|
bool HadError = false;
|
|
|
|
for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) {
|
|
ParmVarDecl *NewParam = New->getParamDecl(i);
|
|
ParmVarDecl *OldParam = Old->getParamDecl(i);
|
|
|
|
// HLSL parameter declarations for inout and out must match between
|
|
// declarations. In HLSL inout and out are ambiguous at the call site,
|
|
// but have different calling behavior, so you cannot overload a
|
|
// method based on a difference between inout and out annotations.
|
|
const auto *NDAttr = NewParam->getAttr<HLSLParamModifierAttr>();
|
|
unsigned NSpellingIdx = (NDAttr ? NDAttr->getSpellingListIndex() : 0);
|
|
const auto *ODAttr = OldParam->getAttr<HLSLParamModifierAttr>();
|
|
unsigned OSpellingIdx = (ODAttr ? ODAttr->getSpellingListIndex() : 0);
|
|
|
|
if (NSpellingIdx != OSpellingIdx) {
|
|
SemaRef.Diag(NewParam->getLocation(),
|
|
diag::err_hlsl_param_qualifier_mismatch)
|
|
<< NDAttr << NewParam;
|
|
SemaRef.Diag(OldParam->getLocation(), diag::note_previous_declaration_as)
|
|
<< ODAttr;
|
|
HadError = true;
|
|
}
|
|
}
|
|
return HadError;
|
|
}
|
|
|
|
// Generally follows PerformScalarCast, with cases reordered for
|
|
// clarity of what types are supported
|
|
bool SemaHLSL::CanPerformScalarCast(QualType SrcTy, QualType DestTy) {
|
|
|
|
if (!SrcTy->isScalarType() || !DestTy->isScalarType())
|
|
return false;
|
|
|
|
if (SemaRef.getASTContext().hasSameUnqualifiedType(SrcTy, DestTy))
|
|
return true;
|
|
|
|
switch (SrcTy->getScalarTypeKind()) {
|
|
case Type::STK_Bool: // casting from bool is like casting from an integer
|
|
case Type::STK_Integral:
|
|
switch (DestTy->getScalarTypeKind()) {
|
|
case Type::STK_Bool:
|
|
case Type::STK_Integral:
|
|
case Type::STK_Floating:
|
|
return true;
|
|
case Type::STK_CPointer:
|
|
case Type::STK_ObjCObjectPointer:
|
|
case Type::STK_BlockPointer:
|
|
case Type::STK_MemberPointer:
|
|
llvm_unreachable("HLSL doesn't support pointers.");
|
|
case Type::STK_IntegralComplex:
|
|
case Type::STK_FloatingComplex:
|
|
llvm_unreachable("HLSL doesn't support complex types.");
|
|
case Type::STK_FixedPoint:
|
|
llvm_unreachable("HLSL doesn't support fixed point types.");
|
|
}
|
|
llvm_unreachable("Should have returned before this");
|
|
|
|
case Type::STK_Floating:
|
|
switch (DestTy->getScalarTypeKind()) {
|
|
case Type::STK_Floating:
|
|
case Type::STK_Bool:
|
|
case Type::STK_Integral:
|
|
return true;
|
|
case Type::STK_FloatingComplex:
|
|
case Type::STK_IntegralComplex:
|
|
llvm_unreachable("HLSL doesn't support complex types.");
|
|
case Type::STK_FixedPoint:
|
|
llvm_unreachable("HLSL doesn't support fixed point types.");
|
|
case Type::STK_CPointer:
|
|
case Type::STK_ObjCObjectPointer:
|
|
case Type::STK_BlockPointer:
|
|
case Type::STK_MemberPointer:
|
|
llvm_unreachable("HLSL doesn't support pointers.");
|
|
}
|
|
llvm_unreachable("Should have returned before this");
|
|
|
|
case Type::STK_MemberPointer:
|
|
case Type::STK_CPointer:
|
|
case Type::STK_BlockPointer:
|
|
case Type::STK_ObjCObjectPointer:
|
|
llvm_unreachable("HLSL doesn't support pointers.");
|
|
|
|
case Type::STK_FixedPoint:
|
|
llvm_unreachable("HLSL doesn't support fixed point types.");
|
|
|
|
case Type::STK_FloatingComplex:
|
|
case Type::STK_IntegralComplex:
|
|
llvm_unreachable("HLSL doesn't support complex types.");
|
|
}
|
|
|
|
llvm_unreachable("Unhandled scalar cast");
|
|
}
|
|
|
|
// Detect if a type contains a bitfield. Will be removed when
|
|
// bitfield support is added to HLSLElementwiseCast and HLSLAggregateSplatCast
|
|
bool SemaHLSL::ContainsBitField(QualType BaseTy) {
|
|
llvm::SmallVector<QualType, 16> WorkList;
|
|
WorkList.push_back(BaseTy);
|
|
while (!WorkList.empty()) {
|
|
QualType T = WorkList.pop_back_val();
|
|
T = T.getCanonicalType().getUnqualifiedType();
|
|
// only check aggregate types
|
|
if (const auto *AT = dyn_cast<ConstantArrayType>(T)) {
|
|
WorkList.push_back(AT->getElementType());
|
|
continue;
|
|
}
|
|
if (const auto *RT = dyn_cast<RecordType>(T)) {
|
|
const RecordDecl *RD = RT->getDecl();
|
|
if (RD->isUnion())
|
|
continue;
|
|
|
|
const CXXRecordDecl *CXXD = dyn_cast<CXXRecordDecl>(RD);
|
|
|
|
if (CXXD && CXXD->isStandardLayout())
|
|
RD = CXXD->getStandardLayoutBaseWithFields();
|
|
|
|
for (const auto *FD : RD->fields()) {
|
|
if (FD->isBitField())
|
|
return true;
|
|
WorkList.push_back(FD->getType());
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Can perform an HLSL Aggregate splat cast if the Dest is an aggregate and the
|
|
// Src is a scalar or a vector of length 1
|
|
// Or if Dest is a vector and Src is a vector of length 1
|
|
bool SemaHLSL::CanPerformAggregateSplatCast(Expr *Src, QualType DestTy) {
|
|
|
|
QualType SrcTy = Src->getType();
|
|
// Not a valid HLSL Aggregate Splat cast if Dest is a scalar or if this is
|
|
// going to be a vector splat from a scalar.
|
|
if ((SrcTy->isScalarType() && DestTy->isVectorType()) ||
|
|
DestTy->isScalarType())
|
|
return false;
|
|
|
|
const VectorType *SrcVecTy = SrcTy->getAs<VectorType>();
|
|
|
|
// Src isn't a scalar or a vector of length 1
|
|
if (!SrcTy->isScalarType() && !(SrcVecTy && SrcVecTy->getNumElements() == 1))
|
|
return false;
|
|
|
|
if (SrcVecTy)
|
|
SrcTy = SrcVecTy->getElementType();
|
|
|
|
if (ContainsBitField(DestTy))
|
|
return false;
|
|
|
|
llvm::SmallVector<QualType> DestTypes;
|
|
BuildFlattenedTypeList(DestTy, DestTypes);
|
|
|
|
for (unsigned I = 0, Size = DestTypes.size(); I < Size; ++I) {
|
|
if (DestTypes[I]->isUnionType())
|
|
return false;
|
|
if (!CanPerformScalarCast(SrcTy, DestTypes[I]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Can we perform an HLSL Elementwise cast?
|
|
// TODO: update this code when matrices are added; see issue #88060
|
|
bool SemaHLSL::CanPerformElementwiseCast(Expr *Src, QualType DestTy) {
|
|
|
|
// Don't handle casts where LHS and RHS are any combination of scalar/vector
|
|
// There must be an aggregate somewhere
|
|
QualType SrcTy = Src->getType();
|
|
if (SrcTy->isScalarType()) // always a splat and this cast doesn't handle that
|
|
return false;
|
|
|
|
if (SrcTy->isVectorType() &&
|
|
(DestTy->isScalarType() || DestTy->isVectorType()))
|
|
return false;
|
|
|
|
if (ContainsBitField(DestTy) || ContainsBitField(SrcTy))
|
|
return false;
|
|
|
|
llvm::SmallVector<QualType> DestTypes;
|
|
BuildFlattenedTypeList(DestTy, DestTypes);
|
|
llvm::SmallVector<QualType> SrcTypes;
|
|
BuildFlattenedTypeList(SrcTy, SrcTypes);
|
|
|
|
// Usually the size of SrcTypes must be greater than or equal to the size of
|
|
// DestTypes.
|
|
if (SrcTypes.size() < DestTypes.size())
|
|
return false;
|
|
|
|
unsigned SrcSize = SrcTypes.size();
|
|
unsigned DstSize = DestTypes.size();
|
|
unsigned I;
|
|
for (I = 0; I < DstSize && I < SrcSize; I++) {
|
|
if (SrcTypes[I]->isUnionType() || DestTypes[I]->isUnionType())
|
|
return false;
|
|
if (!CanPerformScalarCast(SrcTypes[I], DestTypes[I])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check the rest of the source type for unions.
|
|
for (; I < SrcSize; I++) {
|
|
if (SrcTypes[I]->isUnionType())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
|
|
assert(Param->hasAttr<HLSLParamModifierAttr>() &&
|
|
"We should not get here without a parameter modifier expression");
|
|
const auto *Attr = Param->getAttr<HLSLParamModifierAttr>();
|
|
if (Attr->getABI() == ParameterABI::Ordinary)
|
|
return ExprResult(Arg);
|
|
|
|
bool IsInOut = Attr->getABI() == ParameterABI::HLSLInOut;
|
|
if (!Arg->isLValue()) {
|
|
SemaRef.Diag(Arg->getBeginLoc(), diag::error_hlsl_inout_lvalue)
|
|
<< Arg << (IsInOut ? 1 : 0);
|
|
return ExprError();
|
|
}
|
|
|
|
ASTContext &Ctx = SemaRef.getASTContext();
|
|
|
|
QualType Ty = Param->getType().getNonLValueExprType(Ctx);
|
|
|
|
// HLSL allows implicit conversions from scalars to vectors, but not the
|
|
// inverse, so we need to disallow `inout` with scalar->vector or
|
|
// scalar->matrix conversions.
|
|
if (Arg->getType()->isScalarType() != Ty->isScalarType()) {
|
|
SemaRef.Diag(Arg->getBeginLoc(), diag::error_hlsl_inout_scalar_extension)
|
|
<< Arg << (IsInOut ? 1 : 0);
|
|
return ExprError();
|
|
}
|
|
|
|
auto *ArgOpV = new (Ctx) OpaqueValueExpr(Param->getBeginLoc(), Arg->getType(),
|
|
VK_LValue, OK_Ordinary, Arg);
|
|
|
|
// Parameters are initialized via copy initialization. This allows for
|
|
// overload resolution of argument constructors.
|
|
InitializedEntity Entity =
|
|
InitializedEntity::InitializeParameter(Ctx, Ty, false);
|
|
ExprResult Res =
|
|
SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), ArgOpV);
|
|
if (Res.isInvalid())
|
|
return ExprError();
|
|
Expr *Base = Res.get();
|
|
// After the cast, drop the reference type when creating the exprs.
|
|
Ty = Ty.getNonLValueExprType(Ctx);
|
|
auto *OpV = new (Ctx)
|
|
OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Base);
|
|
|
|
// Writebacks are performed with `=` binary operator, which allows for
|
|
// overload resolution on writeback result expressions.
|
|
Res = SemaRef.ActOnBinOp(SemaRef.getCurScope(), Param->getBeginLoc(),
|
|
tok::equal, ArgOpV, OpV);
|
|
|
|
if (Res.isInvalid())
|
|
return ExprError();
|
|
Expr *Writeback = Res.get();
|
|
auto *OutExpr =
|
|
HLSLOutArgExpr::Create(Ctx, Ty, ArgOpV, OpV, Writeback, IsInOut);
|
|
|
|
return ExprResult(OutExpr);
|
|
}
|
|
|
|
QualType SemaHLSL::getInoutParameterType(QualType Ty) {
|
|
// If HLSL gains support for references, all the cites that use this will need
|
|
// to be updated with semantic checking to produce errors for
|
|
// pointers/references.
|
|
assert(!Ty->isReferenceType() &&
|
|
"Pointer and reference types cannot be inout or out parameters");
|
|
Ty = SemaRef.getASTContext().getLValueReferenceType(Ty);
|
|
Ty.addRestrict();
|
|
return Ty;
|
|
}
|
|
|
|
static bool IsDefaultBufferConstantDecl(VarDecl *VD) {
|
|
QualType QT = VD->getType();
|
|
return VD->getDeclContext()->isTranslationUnit() &&
|
|
QT.getAddressSpace() == LangAS::Default &&
|
|
VD->getStorageClass() != SC_Static &&
|
|
!isInvalidConstantBufferLeafElementType(QT.getTypePtr());
|
|
}
|
|
|
|
void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
|
|
// The variable already has an address space (groupshared for ex).
|
|
if (Decl->getType().hasAddressSpace())
|
|
return;
|
|
|
|
if (Decl->getType()->isDependentType())
|
|
return;
|
|
|
|
QualType Type = Decl->getType();
|
|
if (Type->isSamplerT() || Type->isVoidType())
|
|
return;
|
|
|
|
// Resource handles.
|
|
if (isResourceRecordTypeOrArrayOf(Type->getUnqualifiedDesugaredType()))
|
|
return;
|
|
|
|
// Only static globals belong to the Private address space.
|
|
// Non-static globals belongs to the cbuffer.
|
|
if (Decl->getStorageClass() != SC_Static && !Decl->isStaticDataMember())
|
|
return;
|
|
|
|
LangAS ImplAS = LangAS::hlsl_private;
|
|
Type = SemaRef.getASTContext().getAddrSpaceQualType(Type, ImplAS);
|
|
Decl->setType(Type);
|
|
}
|
|
|
|
void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) {
|
|
if (VD->hasGlobalStorage()) {
|
|
// make sure the declaration has a complete type
|
|
if (SemaRef.RequireCompleteType(
|
|
VD->getLocation(),
|
|
SemaRef.getASTContext().getBaseElementType(VD->getType()),
|
|
diag::err_typecheck_decl_incomplete_type)) {
|
|
VD->setInvalidDecl();
|
|
deduceAddressSpace(VD);
|
|
return;
|
|
}
|
|
|
|
// Global variables outside a cbuffer block that are not a resource, static,
|
|
// groupshared, or an empty array or struct belong to the default constant
|
|
// buffer $Globals (to be created at the end of the translation unit).
|
|
if (IsDefaultBufferConstantDecl(VD)) {
|
|
// update address space to hlsl_constant
|
|
QualType NewTy = getASTContext().getAddrSpaceQualType(
|
|
VD->getType(), LangAS::hlsl_constant);
|
|
VD->setType(NewTy);
|
|
DefaultCBufferDecls.push_back(VD);
|
|
}
|
|
|
|
// find all resources bindings on decl
|
|
if (VD->getType()->isHLSLIntangibleType())
|
|
collectResourceBindingsOnVarDecl(VD);
|
|
|
|
const Type *VarType = VD->getType().getTypePtr();
|
|
while (VarType->isArrayType())
|
|
VarType = VarType->getArrayElementTypeNoTypeQual();
|
|
if (VarType->isHLSLResourceRecord()) {
|
|
// Make the variable for resources static. The global externally visible
|
|
// storage is accessed through the handle, which is a member. The variable
|
|
// itself is not externally visible.
|
|
VD->setStorageClass(StorageClass::SC_Static);
|
|
}
|
|
|
|
// process explicit bindings
|
|
processExplicitBindingsOnDecl(VD);
|
|
}
|
|
|
|
deduceAddressSpace(VD);
|
|
}
|
|
|
|
// Walks though the global variable declaration, collects all resource binding
|
|
// requirements and adds them to Bindings
|
|
void SemaHLSL::collectResourceBindingsOnVarDecl(VarDecl *VD) {
|
|
assert(VD->hasGlobalStorage() && VD->getType()->isHLSLIntangibleType() &&
|
|
"expected global variable that contains HLSL resource");
|
|
|
|
// Cbuffers and Tbuffers are HLSLBufferDecl types
|
|
if (const HLSLBufferDecl *CBufferOrTBuffer = dyn_cast<HLSLBufferDecl>(VD)) {
|
|
Bindings.addDeclBindingInfo(VD, CBufferOrTBuffer->isCBuffer()
|
|
? ResourceClass::CBuffer
|
|
: ResourceClass::SRV);
|
|
return;
|
|
}
|
|
|
|
// Unwrap arrays
|
|
// FIXME: Calculate array size while unwrapping
|
|
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
|
|
while (Ty->isConstantArrayType()) {
|
|
const ConstantArrayType *CAT = cast<ConstantArrayType>(Ty);
|
|
Ty = CAT->getElementType()->getUnqualifiedDesugaredType();
|
|
}
|
|
|
|
// Resource (or array of resources)
|
|
if (const HLSLAttributedResourceType *AttrResType =
|
|
HLSLAttributedResourceType::findHandleTypeOnResource(Ty)) {
|
|
Bindings.addDeclBindingInfo(VD, AttrResType->getAttrs().ResourceClass);
|
|
return;
|
|
}
|
|
|
|
// User defined record type
|
|
if (const RecordType *RT = dyn_cast<RecordType>(Ty))
|
|
collectResourceBindingsOnUserRecordDecl(VD, RT);
|
|
}
|
|
|
|
// Walks though the explicit resource binding attributes on the declaration,
|
|
// and makes sure there is a resource that matched the binding and updates
|
|
// DeclBindingInfoLists
|
|
void SemaHLSL::processExplicitBindingsOnDecl(VarDecl *VD) {
|
|
assert(VD->hasGlobalStorage() && "expected global variable");
|
|
|
|
for (Attr *A : VD->attrs()) {
|
|
HLSLResourceBindingAttr *RBA = dyn_cast<HLSLResourceBindingAttr>(A);
|
|
if (!RBA)
|
|
continue;
|
|
|
|
RegisterType RT = RBA->getRegisterType();
|
|
assert(RT != RegisterType::I && "invalid or obsolete register type should "
|
|
"never have an attribute created");
|
|
|
|
if (RT == RegisterType::C) {
|
|
if (Bindings.hasBindingInfoForDecl(VD))
|
|
SemaRef.Diag(VD->getLocation(),
|
|
diag::warn_hlsl_user_defined_type_missing_member)
|
|
<< static_cast<int>(RT);
|
|
continue;
|
|
}
|
|
|
|
// Find DeclBindingInfo for this binding and update it, or report error
|
|
// if it does not exist (user type does to contain resources with the
|
|
// expected resource class).
|
|
ResourceClass RC = getResourceClass(RT);
|
|
if (DeclBindingInfo *BI = Bindings.getDeclBindingInfo(VD, RC)) {
|
|
// update binding info
|
|
BI->setBindingAttribute(RBA, BindingType::Explicit);
|
|
} else {
|
|
SemaRef.Diag(VD->getLocation(),
|
|
diag::warn_hlsl_user_defined_type_missing_member)
|
|
<< static_cast<int>(RT);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool CastInitializer(Sema &S, ASTContext &Ctx, Expr *E,
|
|
llvm::SmallVectorImpl<Expr *> &List,
|
|
llvm::SmallVectorImpl<QualType> &DestTypes) {
|
|
if (List.size() >= DestTypes.size()) {
|
|
List.push_back(E);
|
|
// This is odd, but it isn't technically a failure due to conversion, we
|
|
// handle mismatched counts of arguments differently.
|
|
return true;
|
|
}
|
|
InitializedEntity Entity = InitializedEntity::InitializeParameter(
|
|
Ctx, DestTypes[List.size()], false);
|
|
ExprResult Res = S.PerformCopyInitialization(Entity, E->getBeginLoc(), E);
|
|
if (Res.isInvalid())
|
|
return false;
|
|
Expr *Init = Res.get();
|
|
List.push_back(Init);
|
|
return true;
|
|
}
|
|
|
|
static bool BuildInitializerList(Sema &S, ASTContext &Ctx, Expr *E,
|
|
llvm::SmallVectorImpl<Expr *> &List,
|
|
llvm::SmallVectorImpl<QualType> &DestTypes) {
|
|
// If this is an initialization list, traverse the sub initializers.
|
|
if (auto *Init = dyn_cast<InitListExpr>(E)) {
|
|
for (auto *SubInit : Init->inits())
|
|
if (!BuildInitializerList(S, Ctx, SubInit, List, DestTypes))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// If this is a scalar type, just enqueue the expression.
|
|
QualType Ty = E->getType();
|
|
|
|
if (Ty->isScalarType() || (Ty->isRecordType() && !Ty->isAggregateType()))
|
|
return CastInitializer(S, Ctx, E, List, DestTypes);
|
|
|
|
if (auto *VecTy = Ty->getAs<VectorType>()) {
|
|
uint64_t Size = VecTy->getNumElements();
|
|
|
|
QualType SizeTy = Ctx.getSizeType();
|
|
uint64_t SizeTySize = Ctx.getTypeSize(SizeTy);
|
|
for (uint64_t I = 0; I < Size; ++I) {
|
|
auto *Idx = IntegerLiteral::Create(Ctx, llvm::APInt(SizeTySize, I),
|
|
SizeTy, SourceLocation());
|
|
|
|
ExprResult ElExpr = S.CreateBuiltinArraySubscriptExpr(
|
|
E, E->getBeginLoc(), Idx, E->getEndLoc());
|
|
if (ElExpr.isInvalid())
|
|
return false;
|
|
if (!CastInitializer(S, Ctx, ElExpr.get(), List, DestTypes))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (auto *ArrTy = dyn_cast<ConstantArrayType>(Ty.getTypePtr())) {
|
|
uint64_t Size = ArrTy->getZExtSize();
|
|
QualType SizeTy = Ctx.getSizeType();
|
|
uint64_t SizeTySize = Ctx.getTypeSize(SizeTy);
|
|
for (uint64_t I = 0; I < Size; ++I) {
|
|
auto *Idx = IntegerLiteral::Create(Ctx, llvm::APInt(SizeTySize, I),
|
|
SizeTy, SourceLocation());
|
|
ExprResult ElExpr = S.CreateBuiltinArraySubscriptExpr(
|
|
E, E->getBeginLoc(), Idx, E->getEndLoc());
|
|
if (ElExpr.isInvalid())
|
|
return false;
|
|
if (!BuildInitializerList(S, Ctx, ElExpr.get(), List, DestTypes))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (auto *RTy = Ty->getAs<RecordType>()) {
|
|
llvm::SmallVector<const RecordType *> RecordTypes;
|
|
RecordTypes.push_back(RTy);
|
|
while (RecordTypes.back()->getAsCXXRecordDecl()->getNumBases()) {
|
|
CXXRecordDecl *D = RecordTypes.back()->getAsCXXRecordDecl();
|
|
assert(D->getNumBases() == 1 &&
|
|
"HLSL doesn't support multiple inheritance");
|
|
RecordTypes.push_back(D->bases_begin()->getType()->getAs<RecordType>());
|
|
}
|
|
while (!RecordTypes.empty()) {
|
|
const RecordType *RT = RecordTypes.back();
|
|
RecordTypes.pop_back();
|
|
for (auto *FD : RT->getDecl()->fields()) {
|
|
DeclAccessPair Found = DeclAccessPair::make(FD, FD->getAccess());
|
|
DeclarationNameInfo NameInfo(FD->getDeclName(), E->getBeginLoc());
|
|
ExprResult Res = S.BuildFieldReferenceExpr(
|
|
E, false, E->getBeginLoc(), CXXScopeSpec(), FD, Found, NameInfo);
|
|
if (Res.isInvalid())
|
|
return false;
|
|
if (!BuildInitializerList(S, Ctx, Res.get(), List, DestTypes))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static Expr *GenerateInitLists(ASTContext &Ctx, QualType Ty,
|
|
llvm::SmallVectorImpl<Expr *>::iterator &It) {
|
|
if (Ty->isScalarType() || (Ty->isRecordType() && !Ty->isAggregateType())) {
|
|
return *(It++);
|
|
}
|
|
llvm::SmallVector<Expr *> Inits;
|
|
assert(!isa<MatrixType>(Ty) && "Matrix types not yet supported in HLSL");
|
|
Ty = Ty.getDesugaredType(Ctx);
|
|
if (Ty->isVectorType() || Ty->isConstantArrayType()) {
|
|
QualType ElTy;
|
|
uint64_t Size = 0;
|
|
if (auto *ATy = Ty->getAs<VectorType>()) {
|
|
ElTy = ATy->getElementType();
|
|
Size = ATy->getNumElements();
|
|
} else {
|
|
auto *VTy = cast<ConstantArrayType>(Ty.getTypePtr());
|
|
ElTy = VTy->getElementType();
|
|
Size = VTy->getZExtSize();
|
|
}
|
|
for (uint64_t I = 0; I < Size; ++I)
|
|
Inits.push_back(GenerateInitLists(Ctx, ElTy, It));
|
|
}
|
|
if (auto *RTy = Ty->getAs<RecordType>()) {
|
|
llvm::SmallVector<const RecordType *> RecordTypes;
|
|
RecordTypes.push_back(RTy);
|
|
while (RecordTypes.back()->getAsCXXRecordDecl()->getNumBases()) {
|
|
CXXRecordDecl *D = RecordTypes.back()->getAsCXXRecordDecl();
|
|
assert(D->getNumBases() == 1 &&
|
|
"HLSL doesn't support multiple inheritance");
|
|
RecordTypes.push_back(D->bases_begin()->getType()->getAs<RecordType>());
|
|
}
|
|
while (!RecordTypes.empty()) {
|
|
const RecordType *RT = RecordTypes.back();
|
|
RecordTypes.pop_back();
|
|
for (auto *FD : RT->getDecl()->fields()) {
|
|
Inits.push_back(GenerateInitLists(Ctx, FD->getType(), It));
|
|
}
|
|
}
|
|
}
|
|
auto *NewInit = new (Ctx) InitListExpr(Ctx, Inits.front()->getBeginLoc(),
|
|
Inits, Inits.back()->getEndLoc());
|
|
NewInit->setType(Ty);
|
|
return NewInit;
|
|
}
|
|
|
|
bool SemaHLSL::TransformInitList(const InitializedEntity &Entity,
|
|
const InitializationKind &Kind,
|
|
InitListExpr *Init) {
|
|
// If the initializer is a scalar, just return it.
|
|
if (Init->getType()->isScalarType())
|
|
return true;
|
|
ASTContext &Ctx = SemaRef.getASTContext();
|
|
llvm::SmallVector<QualType, 16> DestTypes;
|
|
// An initializer list might be attempting to initialize a reference or
|
|
// rvalue-reference. When checking the initializer we should look through the
|
|
// reference.
|
|
QualType InitTy = Entity.getType().getNonReferenceType();
|
|
BuildFlattenedTypeList(InitTy, DestTypes);
|
|
|
|
llvm::SmallVector<Expr *, 16> ArgExprs;
|
|
for (unsigned I = 0; I < Init->getNumInits(); ++I) {
|
|
Expr *E = Init->getInit(I);
|
|
if (E->HasSideEffects(Ctx)) {
|
|
QualType Ty = E->getType();
|
|
if (Ty->isRecordType())
|
|
E = new (Ctx) MaterializeTemporaryExpr(Ty, E, E->isLValue());
|
|
E = new (Ctx) OpaqueValueExpr(E->getBeginLoc(), Ty, E->getValueKind(),
|
|
E->getObjectKind(), E);
|
|
Init->setInit(I, E);
|
|
}
|
|
if (!BuildInitializerList(SemaRef, Ctx, E, ArgExprs, DestTypes))
|
|
return false;
|
|
}
|
|
|
|
if (DestTypes.size() != ArgExprs.size()) {
|
|
int TooManyOrFew = ArgExprs.size() > DestTypes.size() ? 1 : 0;
|
|
SemaRef.Diag(Init->getBeginLoc(), diag::err_hlsl_incorrect_num_initializers)
|
|
<< TooManyOrFew << InitTy << DestTypes.size() << ArgExprs.size();
|
|
return false;
|
|
}
|
|
|
|
auto It = ArgExprs.begin();
|
|
// GenerateInitLists will always return an InitListExpr here, because the
|
|
// scalar case is handled above.
|
|
auto *NewInit = cast<InitListExpr>(GenerateInitLists(Ctx, InitTy, It));
|
|
Init->resizeInits(Ctx, NewInit->getNumInits());
|
|
for (unsigned I = 0; I < NewInit->getNumInits(); ++I)
|
|
Init->updateInit(Ctx, I, NewInit->getInit(I));
|
|
return true;
|
|
}
|