Fixes #186859 by allowing 1x1 matrices to be splatted like the scalar and vec1 cases. Assisted-by: GitHub Copilot (powered by Claude Opus 4.6)
6176 lines
223 KiB
C++
6176 lines
223 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/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/HLSLResource.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/AST/TypeBase.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/Lookup.h"
|
|
#include "clang/Sema/ParsedAttr.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/Template.h"
|
|
#include "llvm/ADT/ArrayRef.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/Frontend/HLSL/HLSLBinding.h"
|
|
#include "llvm/Frontend/HLSL/RootSignatureValidations.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/DXILABI.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <iterator>
|
|
#include <utility>
|
|
|
|
using namespace clang;
|
|
using namespace clang::hlsl;
|
|
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");
|
|
}
|
|
|
|
static RegisterType getRegisterType(const HLSLAttributedResourceType *ResTy) {
|
|
return getRegisterType(ResTy->getAttrs().ResourceClass);
|
|
}
|
|
|
|
// 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 char getRegisterTypeChar(RegisterType RT) {
|
|
switch (RT) {
|
|
case RegisterType::SRV:
|
|
return 't';
|
|
case RegisterType::UAV:
|
|
return 'u';
|
|
case RegisterType::CBuffer:
|
|
return 'b';
|
|
case RegisterType::Sampler:
|
|
return 's';
|
|
case RegisterType::C:
|
|
return 'c';
|
|
case RegisterType::I:
|
|
return 'i';
|
|
}
|
|
llvm_unreachable("unexpected RegisterType value");
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
static Builtin::ID getSpecConstBuiltinId(const Type *Type) {
|
|
const auto *BT = dyn_cast<BuiltinType>(Type);
|
|
if (!BT) {
|
|
if (!Type->isEnumeralType())
|
|
return Builtin::NotBuiltin;
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_int;
|
|
}
|
|
|
|
switch (BT->getKind()) {
|
|
case BuiltinType::Bool:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_bool;
|
|
case BuiltinType::Short:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_short;
|
|
case BuiltinType::Int:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_int;
|
|
case BuiltinType::LongLong:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_longlong;
|
|
case BuiltinType::UShort:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_ushort;
|
|
case BuiltinType::UInt:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_uint;
|
|
case BuiltinType::ULongLong:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_ulonglong;
|
|
case BuiltinType::Half:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_half;
|
|
case BuiltinType::Float:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_float;
|
|
case BuiltinType::Double:
|
|
return Builtin::BI__builtin_get_spirv_spec_constant_double;
|
|
default:
|
|
return Builtin::NotBuiltin;
|
|
}
|
|
}
|
|
|
|
static StringRef createRegisterString(ASTContext &AST, RegisterType RegType,
|
|
unsigned N) {
|
|
llvm::SmallString<16> Buffer;
|
|
llvm::raw_svector_ostream OS(Buffer);
|
|
OS << getRegisterTypeChar(RegType);
|
|
OS << N;
|
|
return AST.backupStr(OS.str());
|
|
}
|
|
|
|
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, Matrices, and Structs are always aligned to new buffer rows
|
|
if (T->isArrayType() || T->isStructureType() || T->isConstantMatrixType())
|
|
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 auto *RD = T->getAsRecordDecl()) {
|
|
unsigned Size = 0;
|
|
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;
|
|
}
|
|
|
|
static bool isResourceRecordTypeOrArrayOf(QualType Ty) {
|
|
return Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray();
|
|
}
|
|
|
|
static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) {
|
|
return isResourceRecordTypeOrArrayOf(VD->getType());
|
|
}
|
|
|
|
static const HLSLAttributedResourceType *
|
|
getResourceArrayHandleType(QualType QT) {
|
|
assert(QT->isHLSLResourceRecordArray() &&
|
|
"expected array of resource records");
|
|
const Type *Ty = QT->getUnqualifiedDesugaredType();
|
|
while (const ArrayType *AT = dyn_cast<ArrayType>(Ty))
|
|
Ty = AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
|
|
return HLSLAttributedResourceType::findHandleTypeOnResource(Ty);
|
|
}
|
|
|
|
static const HLSLAttributedResourceType *
|
|
getResourceArrayHandleType(VarDecl *VD) {
|
|
return getResourceArrayHandleType(VD->getType());
|
|
}
|
|
|
|
// 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 (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray())
|
|
return true;
|
|
if (const auto *RD = Ty->getAsCXXRecordDecl())
|
|
return RD->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->isHLSLIntangible() || RD->isEmpty())
|
|
return true;
|
|
// check fields
|
|
for (const FieldDecl *Field : RD->fields()) {
|
|
QualType Ty = Field->getType();
|
|
if (isInvalidConstantBufferLeafElementType(Ty.getTypePtr()))
|
|
return true;
|
|
if (const auto *RD = Ty->getAsCXXRecordDecl();
|
|
RD && requiresImplicitBufferLayoutStructure(RD))
|
|
return true;
|
|
}
|
|
// check bases
|
|
for (const CXXBaseSpecifier &Base : RD->bases())
|
|
if (requiresImplicitBufferLayoutStructure(
|
|
Base.getType()->castAsCXXRecordDecl()))
|
|
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);
|
|
};
|
|
}
|
|
|
|
static const Type *createHostLayoutType(Sema &S, const Type *Ty) {
|
|
ASTContext &AST = S.getASTContext();
|
|
if (auto *RD = Ty->getAsCXXRecordDecl()) {
|
|
if (!requiresImplicitBufferLayoutStructure(RD))
|
|
return Ty;
|
|
RD = createHostLayoutStruct(S, RD);
|
|
if (!RD)
|
|
return nullptr;
|
|
return AST.getCanonicalTagType(RD)->getTypePtr();
|
|
}
|
|
|
|
if (const auto *CAT = dyn_cast<ConstantArrayType>(Ty)) {
|
|
const Type *ElementTy = createHostLayoutType(
|
|
S, CAT->getElementType()->getUnqualifiedDesugaredType());
|
|
if (!ElementTy)
|
|
return nullptr;
|
|
return AST
|
|
.getConstantArrayType(QualType(ElementTy, 0), CAT->getSize(), nullptr,
|
|
CAT->getSizeModifier(),
|
|
CAT->getIndexTypeCVRQualifiers())
|
|
.getTypePtr();
|
|
}
|
|
return Ty;
|
|
}
|
|
|
|
// 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;
|
|
|
|
Ty = createHostLayoutType(S, Ty);
|
|
if (!Ty)
|
|
return nullptr;
|
|
|
|
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()->castAsCXXRecordDecl();
|
|
if (requiresImplicitBufferLayoutStructure(BaseDecl)) {
|
|
BaseDecl = createHostLayoutStruct(S, BaseDecl);
|
|
if (BaseDecl) {
|
|
TypeSourceInfo *TSI =
|
|
AST.getTrivialTypeSourceInfo(AST.getCanonicalTagType(BaseDecl));
|
|
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.
|
|
static 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();
|
|
|
|
FieldDecl *FD =
|
|
createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS);
|
|
// Declarations collected for the default $Globals constant buffer have
|
|
// already been checked to have non-empty cbuffer layout, so
|
|
// createFieldForHostLayoutStruct should always succeed. These declarations
|
|
// already have their address space set to hlsl_constant.
|
|
// For declarations in a named cbuffer block
|
|
// createFieldForHostLayoutStruct can still return nullptr if the type
|
|
// is empty (does not have a cbuffer layout).
|
|
assert((FD || VD->getType().getAddressSpace() != LangAS::hlsl_constant) &&
|
|
"host layout field for $Globals decl failed to be created");
|
|
if (FD) {
|
|
// Add the field decl to the layout struct.
|
|
LS->addDecl(FD);
|
|
if (VD->getType().getAddressSpace() != LangAS::hlsl_constant) {
|
|
// 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);
|
|
}
|
|
|
|
static void addImplicitBindingAttrToDecl(Sema &S, Decl *D, RegisterType RT,
|
|
uint32_t ImplicitBindingOrderID) {
|
|
auto *Attr =
|
|
HLSLResourceBindingAttr::CreateImplicit(S.getASTContext(), "", "0", {});
|
|
Attr->setBinding(RT, std::nullopt, 0);
|
|
Attr->setImplicitBindingOrderID(ImplicitBindingOrderID);
|
|
D->addAttr(Attr);
|
|
}
|
|
|
|
// Handle end of cbuffer/tbuffer declaration
|
|
void SemaHLSL::ActOnFinishBuffer(Decl *Dcl, SourceLocation RBrace) {
|
|
auto *BufDecl = cast<HLSLBufferDecl>(Dcl);
|
|
BufDecl->setRBraceLoc(RBrace);
|
|
|
|
validatePackoffset(SemaRef, BufDecl);
|
|
|
|
createHostLayoutStructForBuffer(SemaRef, BufDecl);
|
|
|
|
// Handle implicit binding if needed.
|
|
ResourceBindingAttrs ResourceAttrs(Dcl);
|
|
if (!ResourceAttrs.isExplicit()) {
|
|
SemaRef.Diag(Dcl->getLocation(), diag::warn_hlsl_implicit_binding);
|
|
// Use HLSLResourceBindingAttr to transfer implicit binding order_ID
|
|
// to codegen. If it does not exist, create an implicit attribute.
|
|
uint32_t OrderID = getNextImplicitBindingOrderID();
|
|
if (ResourceAttrs.hasBinding())
|
|
ResourceAttrs.setImplicitOrderID(OrderID);
|
|
else
|
|
addImplicitBindingAttrToDecl(SemaRef, BufDecl,
|
|
BufDecl->isCBuffer() ? RegisterType::CBuffer
|
|
: RegisterType::SRV,
|
|
OrderID);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
HLSLVkConstantIdAttr *
|
|
SemaHLSL::mergeVkConstantIdAttr(Decl *D, const AttributeCommonInfo &AL,
|
|
int Id) {
|
|
|
|
auto &TargetInfo = getASTContext().getTargetInfo();
|
|
if (TargetInfo.getTriple().getArch() != llvm::Triple::spirv) {
|
|
Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL;
|
|
return nullptr;
|
|
}
|
|
|
|
auto *VD = cast<VarDecl>(D);
|
|
|
|
if (getSpecConstBuiltinId(VD->getType()->getUnqualifiedDesugaredType()) ==
|
|
Builtin::NotBuiltin) {
|
|
Diag(VD->getLocation(), diag::err_specialization_const);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!VD->getType().isConstQualified()) {
|
|
Diag(VD->getLocation(), diag::err_specialization_const);
|
|
return nullptr;
|
|
}
|
|
|
|
if (HLSLVkConstantIdAttr *CI = D->getAttr<HLSLVkConstantIdAttr>()) {
|
|
if (CI->getId() != Id) {
|
|
Diag(CI->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL;
|
|
Diag(AL.getLoc(), diag::note_conflicting_attribute);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
HLSLVkConstantIdAttr *Result =
|
|
::new (getASTContext()) HLSLVkConstantIdAttr(getASTContext(), AL, Id);
|
|
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;
|
|
|
|
// If we have specified a root signature to override the entry function then
|
|
// attach it now
|
|
HLSLRootSignatureDecl *SignatureDecl =
|
|
lookupRootSignatureOverrideDecl(FD->getDeclContext());
|
|
if (SignatureDecl) {
|
|
FD->dropAttr<RootSignatureAttr>();
|
|
// We could look up the SourceRange of the macro here as well
|
|
AttributeCommonInfo AL(RootSigOverrideIdent, AttributeScopeInfo(),
|
|
SourceRange(), ParsedAttr::Form::Microsoft());
|
|
FD->addAttr(::new (getASTContext()) RootSignatureAttr(
|
|
getASTContext(), AL, RootSigOverrideIdent, SignatureDecl));
|
|
}
|
|
|
|
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;
|
|
case llvm::Triple::RootSignature:
|
|
llvm_unreachable("rootsig environment has no functions");
|
|
default:
|
|
llvm_unreachable("Unhandled environment in triple");
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool isVkPipelineBuiltin(const ASTContext &AstContext, FunctionDecl *FD,
|
|
HLSLAppliedSemanticAttr *Semantic,
|
|
bool IsInput) {
|
|
if (AstContext.getTargetInfo().getTriple().getOS() != llvm::Triple::Vulkan)
|
|
return false;
|
|
|
|
const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
|
|
assert(ShaderAttr && "Entry point has no shader attribute");
|
|
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
|
|
auto SemanticName = Semantic->getSemanticName().upper();
|
|
|
|
// The SV_Position semantic is lowered to:
|
|
// - Position built-in for vertex output.
|
|
// - FragCoord built-in for fragment input.
|
|
if (SemanticName == "SV_POSITION") {
|
|
return (ST == llvm::Triple::Vertex && !IsInput) ||
|
|
(ST == llvm::Triple::Pixel && IsInput);
|
|
}
|
|
if (SemanticName == "SV_VERTEXID")
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
|
|
DeclaratorDecl *OutputDecl,
|
|
DeclaratorDecl *D,
|
|
SemanticInfo &ActiveSemantic,
|
|
SemaHLSL::SemanticContext &SC) {
|
|
if (ActiveSemantic.Semantic == nullptr) {
|
|
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
|
|
if (ActiveSemantic.Semantic)
|
|
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
|
|
}
|
|
|
|
if (!ActiveSemantic.Semantic) {
|
|
Diag(D->getLocation(), diag::err_hlsl_missing_semantic_annotation);
|
|
return false;
|
|
}
|
|
|
|
auto *A = ::new (getASTContext())
|
|
HLSLAppliedSemanticAttr(getASTContext(), *ActiveSemantic.Semantic,
|
|
ActiveSemantic.Semantic->getAttrName()->getName(),
|
|
ActiveSemantic.Index.value_or(0));
|
|
if (!A)
|
|
return false;
|
|
|
|
checkSemanticAnnotation(FD, D, A, SC);
|
|
OutputDecl->addAttr(A);
|
|
|
|
unsigned Location = ActiveSemantic.Index.value_or(0);
|
|
|
|
if (!isVkPipelineBuiltin(getASTContext(), FD, A,
|
|
SC.CurrentIOType & IOType::In)) {
|
|
bool HasVkLocation = false;
|
|
if (auto *A = D->getAttr<HLSLVkLocationAttr>()) {
|
|
HasVkLocation = true;
|
|
Location = A->getLocation();
|
|
}
|
|
|
|
if (SC.UsesExplicitVkLocations.value_or(HasVkLocation) != HasVkLocation) {
|
|
Diag(D->getLocation(), diag::err_hlsl_semantic_partial_explicit_indexing);
|
|
return false;
|
|
}
|
|
SC.UsesExplicitVkLocations = HasVkLocation;
|
|
}
|
|
|
|
const ConstantArrayType *AT = dyn_cast<ConstantArrayType>(D->getType());
|
|
unsigned ElementCount = AT ? AT->getZExtSize() : 1;
|
|
ActiveSemantic.Index = Location + ElementCount;
|
|
|
|
Twine BaseName = Twine(ActiveSemantic.Semantic->getAttrName()->getName());
|
|
for (unsigned I = 0; I < ElementCount; ++I) {
|
|
Twine VariableName = BaseName.concat(Twine(Location + I));
|
|
|
|
auto [_, Inserted] = SC.ActiveSemantics.insert(VariableName.str());
|
|
if (!Inserted) {
|
|
Diag(D->getLocation(), diag::err_hlsl_semantic_index_overlap)
|
|
<< VariableName.str();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
|
|
DeclaratorDecl *OutputDecl,
|
|
DeclaratorDecl *D,
|
|
SemanticInfo &ActiveSemantic,
|
|
SemaHLSL::SemanticContext &SC) {
|
|
if (ActiveSemantic.Semantic == nullptr) {
|
|
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
|
|
if (ActiveSemantic.Semantic)
|
|
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
|
|
}
|
|
|
|
const Type *T = D == FD ? &*FD->getReturnType() : &*D->getType();
|
|
T = T->getUnqualifiedDesugaredType();
|
|
|
|
const RecordType *RT = dyn_cast<RecordType>(T);
|
|
if (!RT)
|
|
return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
|
|
SC);
|
|
|
|
const RecordDecl *RD = RT->getDecl();
|
|
for (FieldDecl *Field : RD->fields()) {
|
|
SemanticInfo Info = ActiveSemantic;
|
|
if (!determineActiveSemantic(FD, OutputDecl, Field, Info, SC)) {
|
|
Diag(Field->getLocation(), diag::note_hlsl_semantic_used_here) << Field;
|
|
return false;
|
|
}
|
|
if (ActiveSemantic.Semantic)
|
|
ActiveSemantic = Info;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
case llvm::Triple::RootSignature:
|
|
llvm_unreachable("rootsig environment has no function entry point");
|
|
default:
|
|
llvm_unreachable("Unhandled environment in triple");
|
|
}
|
|
|
|
SemaHLSL::SemanticContext InputSC = {};
|
|
InputSC.CurrentIOType = IOType::In;
|
|
|
|
for (ParmVarDecl *Param : FD->parameters()) {
|
|
SemanticInfo ActiveSemantic;
|
|
ActiveSemantic.Semantic = Param->getAttr<HLSLParsedSemanticAttr>();
|
|
if (ActiveSemantic.Semantic)
|
|
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
|
|
|
|
// FIXME: Verify output semantics in parameters.
|
|
if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic, InputSC)) {
|
|
Diag(Param->getLocation(), diag::note_previous_decl) << Param;
|
|
FD->setInvalidDecl();
|
|
}
|
|
}
|
|
|
|
SemanticInfo ActiveSemantic;
|
|
SemaHLSL::SemanticContext OutputSC = {};
|
|
OutputSC.CurrentIOType = IOType::Out;
|
|
ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>();
|
|
if (ActiveSemantic.Semantic)
|
|
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
|
|
if (!FD->getReturnType()->isVoidType())
|
|
determineActiveSemantic(FD, FD, FD, ActiveSemantic, OutputSC);
|
|
}
|
|
|
|
void SemaHLSL::checkSemanticAnnotation(
|
|
FunctionDecl *EntryPoint, const Decl *Param,
|
|
const HLSLAppliedSemanticAttr *SemanticAttr, const SemanticContext &SC) {
|
|
auto *ShaderAttr = EntryPoint->getAttr<HLSLShaderAttr>();
|
|
assert(ShaderAttr && "Entry point has no shader attribute");
|
|
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
|
|
|
|
auto SemanticName = SemanticAttr->getSemanticName().upper();
|
|
if (SemanticName == "SV_DISPATCHTHREADID" ||
|
|
SemanticName == "SV_GROUPINDEX" || SemanticName == "SV_GROUPTHREADID" ||
|
|
SemanticName == "SV_GROUPID") {
|
|
|
|
if (ST != llvm::Triple::Compute)
|
|
diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
|
|
{{llvm::Triple::Compute, IOType::In}});
|
|
|
|
if (SemanticAttr->getSemanticIndex() != 0) {
|
|
std::string PrettyName =
|
|
"'" + SemanticAttr->getSemanticName().str() + "'";
|
|
Diag(SemanticAttr->getLoc(),
|
|
diag::err_hlsl_semantic_indexing_not_supported)
|
|
<< PrettyName;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_POSITION") {
|
|
// SV_Position can be an input or output in vertex shaders,
|
|
// but only an input in pixel shaders.
|
|
diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
|
|
{{llvm::Triple::Vertex, IOType::InOut},
|
|
{llvm::Triple::Pixel, IOType::In}});
|
|
return;
|
|
}
|
|
if (SemanticName == "SV_VERTEXID") {
|
|
diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
|
|
{{llvm::Triple::Vertex, IOType::In}});
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_TARGET") {
|
|
diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
|
|
{{llvm::Triple::Pixel, IOType::Out}});
|
|
return;
|
|
}
|
|
|
|
// FIXME: catch-all for non-implemented system semantics reaching this
|
|
// location.
|
|
if (SemanticAttr->getAttrName()->getName().starts_with_insensitive("SV_"))
|
|
llvm_unreachable("Unknown SemanticAttr");
|
|
}
|
|
|
|
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, ", ");
|
|
}
|
|
|
|
void SemaHLSL::diagnoseSemanticStageMismatch(
|
|
const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
|
|
std::initializer_list<SemanticStageInfo> Allowed) {
|
|
|
|
for (auto &Case : Allowed) {
|
|
if (Case.Stage != Stage)
|
|
continue;
|
|
|
|
if (CurrentIOType & Case.AllowedIOTypesMask)
|
|
return;
|
|
|
|
SmallVector<std::string, 8> ValidCases;
|
|
llvm::transform(
|
|
Allowed, std::back_inserter(ValidCases), [](SemanticStageInfo Case) {
|
|
SmallVector<std::string, 2> ValidType;
|
|
if (Case.AllowedIOTypesMask & IOType::In)
|
|
ValidType.push_back("input");
|
|
if (Case.AllowedIOTypesMask & IOType::Out)
|
|
ValidType.push_back("output");
|
|
return std::string(
|
|
HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage)) +
|
|
" " + join(ValidType, "/");
|
|
});
|
|
Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_iotype_for_stage)
|
|
<< A->getAttrName() << (CurrentIOType & IOType::In ? "input" : "output")
|
|
<< llvm::Triple::getEnvironmentTypeName(Case.Stage)
|
|
<< join(ValidCases, ", ");
|
|
return;
|
|
}
|
|
|
|
SmallVector<StringRef, 8> StageStrings;
|
|
llvm::transform(
|
|
Allowed, std::back_inserter(StageStrings), [](SemanticStageInfo Case) {
|
|
return StringRef(
|
|
HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage));
|
|
});
|
|
|
|
Diag(A->getLoc(), diag::err_hlsl_attr_unsupported_in_stage)
|
|
<< A->getAttrName() << llvm::Triple::getEnvironmentTypeName(Stage)
|
|
<< (Allowed.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());
|
|
}
|
|
|
|
std::pair<IdentifierInfo *, bool>
|
|
SemaHLSL::ActOnStartRootSignatureDecl(StringRef Signature) {
|
|
llvm::hash_code Hash = llvm::hash_value(Signature);
|
|
std::string IdStr = "__hlsl_rootsig_decl_" + std::to_string(Hash);
|
|
IdentifierInfo *DeclIdent = &(getASTContext().Idents.get(IdStr));
|
|
|
|
// Check if we have already found a decl of the same name.
|
|
LookupResult R(SemaRef, DeclIdent, SourceLocation(),
|
|
Sema::LookupOrdinaryName);
|
|
bool Found = SemaRef.LookupQualifiedName(R, SemaRef.CurContext);
|
|
return {DeclIdent, Found};
|
|
}
|
|
|
|
void SemaHLSL::ActOnFinishRootSignatureDecl(
|
|
SourceLocation Loc, IdentifierInfo *DeclIdent,
|
|
ArrayRef<hlsl::RootSignatureElement> RootElements) {
|
|
|
|
if (handleRootSignatureElements(RootElements))
|
|
return;
|
|
|
|
SmallVector<llvm::hlsl::rootsig::RootElement> Elements;
|
|
for (auto &RootSigElement : RootElements)
|
|
Elements.push_back(RootSigElement.getElement());
|
|
|
|
auto *SignatureDecl = HLSLRootSignatureDecl::Create(
|
|
SemaRef.getASTContext(), /*DeclContext=*/SemaRef.CurContext, Loc,
|
|
DeclIdent, SemaRef.getLangOpts().HLSLRootSigVer, Elements);
|
|
|
|
SignatureDecl->setImplicit();
|
|
SemaRef.PushOnScopeChains(SignatureDecl, SemaRef.getCurScope());
|
|
}
|
|
|
|
HLSLRootSignatureDecl *
|
|
SemaHLSL::lookupRootSignatureOverrideDecl(DeclContext *DC) const {
|
|
if (RootSigOverrideIdent) {
|
|
LookupResult R(SemaRef, RootSigOverrideIdent, SourceLocation(),
|
|
Sema::LookupOrdinaryName);
|
|
if (SemaRef.LookupQualifiedName(R, DC))
|
|
return dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl());
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct PerVisibilityBindingChecker {
|
|
SemaHLSL *S;
|
|
// We need one builder per `llvm::dxbc::ShaderVisibility` value.
|
|
std::array<llvm::hlsl::BindingInfoBuilder, 8> Builders;
|
|
|
|
struct ElemInfo {
|
|
const hlsl::RootSignatureElement *Elem;
|
|
llvm::dxbc::ShaderVisibility Vis;
|
|
bool Diagnosed;
|
|
};
|
|
llvm::SmallVector<ElemInfo> ElemInfoMap;
|
|
|
|
PerVisibilityBindingChecker(SemaHLSL *S) : S(S) {}
|
|
|
|
void trackBinding(llvm::dxbc::ShaderVisibility Visibility,
|
|
llvm::dxil::ResourceClass RC, uint32_t Space,
|
|
uint32_t LowerBound, uint32_t UpperBound,
|
|
const hlsl::RootSignatureElement *Elem) {
|
|
uint32_t BuilderIndex = llvm::to_underlying(Visibility);
|
|
assert(BuilderIndex < Builders.size() &&
|
|
"Not enough builders for visibility type");
|
|
Builders[BuilderIndex].trackBinding(RC, Space, LowerBound, UpperBound,
|
|
static_cast<const void *>(Elem));
|
|
|
|
static_assert(llvm::to_underlying(llvm::dxbc::ShaderVisibility::All) == 0,
|
|
"'All' visibility must come first");
|
|
if (Visibility == llvm::dxbc::ShaderVisibility::All)
|
|
for (size_t I = 1, E = Builders.size(); I < E; ++I)
|
|
Builders[I].trackBinding(RC, Space, LowerBound, UpperBound,
|
|
static_cast<const void *>(Elem));
|
|
|
|
ElemInfoMap.push_back({Elem, Visibility, false});
|
|
}
|
|
|
|
ElemInfo &getInfo(const hlsl::RootSignatureElement *Elem) {
|
|
auto It = llvm::lower_bound(
|
|
ElemInfoMap, Elem,
|
|
[](const auto &LHS, const auto &RHS) { return LHS.Elem < RHS; });
|
|
assert(It->Elem == Elem && "Element not in map");
|
|
return *It;
|
|
}
|
|
|
|
bool checkOverlap() {
|
|
llvm::sort(ElemInfoMap, [](const auto &LHS, const auto &RHS) {
|
|
return LHS.Elem < RHS.Elem;
|
|
});
|
|
|
|
bool HadOverlap = false;
|
|
|
|
using llvm::hlsl::BindingInfoBuilder;
|
|
auto ReportOverlap = [this,
|
|
&HadOverlap](const BindingInfoBuilder &Builder,
|
|
const llvm::hlsl::Binding &Reported) {
|
|
HadOverlap = true;
|
|
|
|
const auto *Elem =
|
|
static_cast<const hlsl::RootSignatureElement *>(Reported.Cookie);
|
|
const llvm::hlsl::Binding &Previous = Builder.findOverlapping(Reported);
|
|
const auto *PrevElem =
|
|
static_cast<const hlsl::RootSignatureElement *>(Previous.Cookie);
|
|
|
|
ElemInfo &Info = getInfo(Elem);
|
|
// We will have already diagnosed this binding if there's overlap in the
|
|
// "All" visibility as well as any particular visibility.
|
|
if (Info.Diagnosed)
|
|
return;
|
|
Info.Diagnosed = true;
|
|
|
|
ElemInfo &PrevInfo = getInfo(PrevElem);
|
|
llvm::dxbc::ShaderVisibility CommonVis =
|
|
Info.Vis == llvm::dxbc::ShaderVisibility::All ? PrevInfo.Vis
|
|
: Info.Vis;
|
|
|
|
this->S->Diag(Elem->getLocation(), diag::err_hlsl_resource_range_overlap)
|
|
<< llvm::to_underlying(Reported.RC) << Reported.LowerBound
|
|
<< Reported.isUnbounded() << Reported.UpperBound
|
|
<< llvm::to_underlying(Previous.RC) << Previous.LowerBound
|
|
<< Previous.isUnbounded() << Previous.UpperBound << Reported.Space
|
|
<< CommonVis;
|
|
|
|
this->S->Diag(PrevElem->getLocation(),
|
|
diag::note_hlsl_resource_range_here);
|
|
};
|
|
|
|
for (BindingInfoBuilder &Builder : Builders)
|
|
Builder.calculateBindingInfo(ReportOverlap);
|
|
|
|
return HadOverlap;
|
|
}
|
|
};
|
|
|
|
static CXXMethodDecl *lookupMethod(Sema &S, CXXRecordDecl *RecordDecl,
|
|
StringRef Name, SourceLocation Loc) {
|
|
DeclarationName DeclName(&S.getASTContext().Idents.get(Name));
|
|
LookupResult Result(S, DeclName, Loc, Sema::LookupMemberName);
|
|
if (!S.LookupQualifiedName(Result, static_cast<DeclContext *>(RecordDecl)))
|
|
return nullptr;
|
|
return cast<CXXMethodDecl>(Result.getFoundDecl());
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
static bool hasCounterHandle(const CXXRecordDecl *RD) {
|
|
if (RD->field_empty())
|
|
return false;
|
|
auto It = std::next(RD->field_begin());
|
|
if (It == RD->field_end())
|
|
return false;
|
|
const FieldDecl *SecondField = *It;
|
|
if (const auto *ResTy =
|
|
SecondField->getType()->getAs<HLSLAttributedResourceType>()) {
|
|
return ResTy->getAttrs().IsCounter;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SemaHLSL::handleRootSignatureElements(
|
|
ArrayRef<hlsl::RootSignatureElement> Elements) {
|
|
// Define some common error handling functions
|
|
bool HadError = false;
|
|
auto ReportError = [this, &HadError](SourceLocation Loc, uint32_t LowerBound,
|
|
uint32_t UpperBound) {
|
|
HadError = true;
|
|
this->Diag(Loc, diag::err_hlsl_invalid_rootsig_value)
|
|
<< LowerBound << UpperBound;
|
|
};
|
|
|
|
auto ReportFloatError = [this, &HadError](SourceLocation Loc,
|
|
float LowerBound,
|
|
float UpperBound) {
|
|
HadError = true;
|
|
this->Diag(Loc, diag::err_hlsl_invalid_rootsig_value)
|
|
<< llvm::formatv("{0:f}", LowerBound).sstr<6>()
|
|
<< llvm::formatv("{0:f}", UpperBound).sstr<6>();
|
|
};
|
|
|
|
auto VerifyRegister = [ReportError](SourceLocation Loc, uint32_t Register) {
|
|
if (!llvm::hlsl::rootsig::verifyRegisterValue(Register))
|
|
ReportError(Loc, 0, 0xfffffffe);
|
|
};
|
|
|
|
auto VerifySpace = [ReportError](SourceLocation Loc, uint32_t Space) {
|
|
if (!llvm::hlsl::rootsig::verifyRegisterSpace(Space))
|
|
ReportError(Loc, 0, 0xffffffef);
|
|
};
|
|
|
|
const uint32_t Version =
|
|
llvm::to_underlying(SemaRef.getLangOpts().HLSLRootSigVer);
|
|
const uint32_t VersionEnum = Version - 1;
|
|
auto ReportFlagError = [this, &HadError, VersionEnum](SourceLocation Loc) {
|
|
HadError = true;
|
|
this->Diag(Loc, diag::err_hlsl_invalid_rootsig_flag)
|
|
<< /*version minor*/ VersionEnum;
|
|
};
|
|
|
|
// Iterate through the elements and do basic validations
|
|
for (const hlsl::RootSignatureElement &RootSigElem : Elements) {
|
|
SourceLocation Loc = RootSigElem.getLocation();
|
|
const llvm::hlsl::rootsig::RootElement &Elem = RootSigElem.getElement();
|
|
if (const auto *Descriptor =
|
|
std::get_if<llvm::hlsl::rootsig::RootDescriptor>(&Elem)) {
|
|
VerifyRegister(Loc, Descriptor->Reg.Number);
|
|
VerifySpace(Loc, Descriptor->Space);
|
|
|
|
if (!llvm::hlsl::rootsig::verifyRootDescriptorFlag(Version,
|
|
Descriptor->Flags))
|
|
ReportFlagError(Loc);
|
|
} else if (const auto *Constants =
|
|
std::get_if<llvm::hlsl::rootsig::RootConstants>(&Elem)) {
|
|
VerifyRegister(Loc, Constants->Reg.Number);
|
|
VerifySpace(Loc, Constants->Space);
|
|
} else if (const auto *Sampler =
|
|
std::get_if<llvm::hlsl::rootsig::StaticSampler>(&Elem)) {
|
|
VerifyRegister(Loc, Sampler->Reg.Number);
|
|
VerifySpace(Loc, Sampler->Space);
|
|
|
|
assert(!std::isnan(Sampler->MaxLOD) && !std::isnan(Sampler->MinLOD) &&
|
|
"By construction, parseFloatParam can't produce a NaN from a "
|
|
"float_literal token");
|
|
|
|
if (!llvm::hlsl::rootsig::verifyMaxAnisotropy(Sampler->MaxAnisotropy))
|
|
ReportError(Loc, 0, 16);
|
|
if (!llvm::hlsl::rootsig::verifyMipLODBias(Sampler->MipLODBias))
|
|
ReportFloatError(Loc, -16.f, 15.99f);
|
|
} else if (const auto *Clause =
|
|
std::get_if<llvm::hlsl::rootsig::DescriptorTableClause>(
|
|
&Elem)) {
|
|
VerifyRegister(Loc, Clause->Reg.Number);
|
|
VerifySpace(Loc, Clause->Space);
|
|
|
|
if (!llvm::hlsl::rootsig::verifyNumDescriptors(Clause->NumDescriptors)) {
|
|
// NumDescriptor could techincally be ~0u but that is reserved for
|
|
// unbounded, so the diagnostic will not report that as a valid int
|
|
// value
|
|
ReportError(Loc, 1, 0xfffffffe);
|
|
}
|
|
|
|
if (!llvm::hlsl::rootsig::verifyDescriptorRangeFlag(Version, Clause->Type,
|
|
Clause->Flags))
|
|
ReportFlagError(Loc);
|
|
}
|
|
}
|
|
|
|
PerVisibilityBindingChecker BindingChecker(this);
|
|
SmallVector<std::pair<const llvm::hlsl::rootsig::DescriptorTableClause *,
|
|
const hlsl::RootSignatureElement *>>
|
|
UnboundClauses;
|
|
|
|
for (const hlsl::RootSignatureElement &RootSigElem : Elements) {
|
|
const llvm::hlsl::rootsig::RootElement &Elem = RootSigElem.getElement();
|
|
if (const auto *Descriptor =
|
|
std::get_if<llvm::hlsl::rootsig::RootDescriptor>(&Elem)) {
|
|
uint32_t LowerBound(Descriptor->Reg.Number);
|
|
uint32_t UpperBound(LowerBound); // inclusive range
|
|
|
|
BindingChecker.trackBinding(
|
|
Descriptor->Visibility,
|
|
static_cast<llvm::dxil::ResourceClass>(Descriptor->Type),
|
|
Descriptor->Space, LowerBound, UpperBound, &RootSigElem);
|
|
} else if (const auto *Constants =
|
|
std::get_if<llvm::hlsl::rootsig::RootConstants>(&Elem)) {
|
|
uint32_t LowerBound(Constants->Reg.Number);
|
|
uint32_t UpperBound(LowerBound); // inclusive range
|
|
|
|
BindingChecker.trackBinding(
|
|
Constants->Visibility, llvm::dxil::ResourceClass::CBuffer,
|
|
Constants->Space, LowerBound, UpperBound, &RootSigElem);
|
|
} else if (const auto *Sampler =
|
|
std::get_if<llvm::hlsl::rootsig::StaticSampler>(&Elem)) {
|
|
uint32_t LowerBound(Sampler->Reg.Number);
|
|
uint32_t UpperBound(LowerBound); // inclusive range
|
|
|
|
BindingChecker.trackBinding(
|
|
Sampler->Visibility, llvm::dxil::ResourceClass::Sampler,
|
|
Sampler->Space, LowerBound, UpperBound, &RootSigElem);
|
|
} else if (const auto *Clause =
|
|
std::get_if<llvm::hlsl::rootsig::DescriptorTableClause>(
|
|
&Elem)) {
|
|
// We'll process these once we see the table element.
|
|
UnboundClauses.emplace_back(Clause, &RootSigElem);
|
|
} else if (const auto *Table =
|
|
std::get_if<llvm::hlsl::rootsig::DescriptorTable>(&Elem)) {
|
|
assert(UnboundClauses.size() == Table->NumClauses &&
|
|
"Number of unbound elements must match the number of clauses");
|
|
bool HasAnySampler = false;
|
|
bool HasAnyNonSampler = false;
|
|
uint64_t Offset = 0;
|
|
bool IsPrevUnbound = false;
|
|
for (const auto &[Clause, ClauseElem] : UnboundClauses) {
|
|
SourceLocation Loc = ClauseElem->getLocation();
|
|
if (Clause->Type == llvm::dxil::ResourceClass::Sampler)
|
|
HasAnySampler = true;
|
|
else
|
|
HasAnyNonSampler = true;
|
|
|
|
if (HasAnySampler && HasAnyNonSampler)
|
|
Diag(Loc, diag::err_hlsl_invalid_mixed_resources);
|
|
|
|
// Relevant error will have already been reported above and needs to be
|
|
// fixed before we can conduct further analysis, so shortcut error
|
|
// return
|
|
if (Clause->NumDescriptors == 0)
|
|
return true;
|
|
|
|
bool IsAppending =
|
|
Clause->Offset == llvm::hlsl::rootsig::DescriptorTableOffsetAppend;
|
|
if (!IsAppending)
|
|
Offset = Clause->Offset;
|
|
|
|
uint64_t RangeBound = llvm::hlsl::rootsig::computeRangeBound(
|
|
Offset, Clause->NumDescriptors);
|
|
|
|
if (IsPrevUnbound && IsAppending)
|
|
Diag(Loc, diag::err_hlsl_appending_onto_unbound);
|
|
else if (!llvm::hlsl::rootsig::verifyNoOverflowedOffset(RangeBound))
|
|
Diag(Loc, diag::err_hlsl_offset_overflow) << Offset << RangeBound;
|
|
|
|
// Update offset to be 1 past this range's bound
|
|
Offset = RangeBound + 1;
|
|
IsPrevUnbound = Clause->NumDescriptors ==
|
|
llvm::hlsl::rootsig::NumDescriptorsUnbounded;
|
|
|
|
// Compute the register bounds and track resource binding
|
|
uint32_t LowerBound(Clause->Reg.Number);
|
|
uint32_t UpperBound = llvm::hlsl::rootsig::computeRangeBound(
|
|
LowerBound, Clause->NumDescriptors);
|
|
|
|
BindingChecker.trackBinding(
|
|
Table->Visibility,
|
|
static_cast<llvm::dxil::ResourceClass>(Clause->Type), Clause->Space,
|
|
LowerBound, UpperBound, ClauseElem);
|
|
}
|
|
UnboundClauses.clear();
|
|
}
|
|
}
|
|
|
|
return BindingChecker.checkOverlap();
|
|
}
|
|
|
|
void SemaHLSL::handleRootSignatureAttr(Decl *D, const ParsedAttr &AL) {
|
|
if (AL.getNumArgs() != 1) {
|
|
Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
|
|
return;
|
|
}
|
|
|
|
IdentifierInfo *Ident = AL.getArgAsIdent(0)->getIdentifierInfo();
|
|
if (auto *RS = D->getAttr<RootSignatureAttr>()) {
|
|
if (RS->getSignatureIdent() != Ident) {
|
|
Diag(AL.getLoc(), diag::err_disallowed_duplicate_attribute) << RS;
|
|
return;
|
|
}
|
|
|
|
Diag(AL.getLoc(), diag::warn_duplicate_attribute_exact) << RS;
|
|
return;
|
|
}
|
|
|
|
LookupResult R(SemaRef, Ident, SourceLocation(), Sema::LookupOrdinaryName);
|
|
if (SemaRef.LookupQualifiedName(R, D->getDeclContext()))
|
|
if (auto *SignatureDecl =
|
|
dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl())) {
|
|
D->addAttr(::new (getASTContext()) RootSignatureAttr(
|
|
getASTContext(), AL, Ident, SignatureDecl));
|
|
}
|
|
}
|
|
|
|
void SemaHLSL::handleNumThreadsAttr(Decl *D, const ParsedAttr &AL) {
|
|
llvm::VersionTuple SMVersion =
|
|
getASTContext().getTargetInfo().getTriple().getOSVersion();
|
|
bool IsDXIL = getASTContext().getTargetInfo().getTriple().getArch() ==
|
|
llvm::Triple::dxil;
|
|
|
|
uint32_t ZMax = 1024;
|
|
uint32_t ThreadMax = 1024;
|
|
if (IsDXIL && SMVersion.getMajor() <= 4) {
|
|
ZMax = 1;
|
|
ThreadMax = 768;
|
|
} else if (IsDXIL && 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);
|
|
}
|
|
|
|
void SemaHLSL::handleVkExtBuiltinInputAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t ID;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), ID))
|
|
return;
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLVkExtBuiltinInputAttr(getASTContext(), AL, ID));
|
|
}
|
|
|
|
void SemaHLSL::handleVkPushConstantAttr(Decl *D, const ParsedAttr &AL) {
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLVkPushConstantAttr(getASTContext(), AL));
|
|
}
|
|
|
|
void SemaHLSL::handleVkConstantIdAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t Id;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Id))
|
|
return;
|
|
HLSLVkConstantIdAttr *NewAttr = mergeVkConstantIdAttr(D, AL, Id);
|
|
if (NewAttr)
|
|
D->addAttr(NewAttr);
|
|
}
|
|
|
|
void SemaHLSL::handleVkBindingAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t Binding = 0;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Binding))
|
|
return;
|
|
uint32_t Set = 0;
|
|
if (AL.getNumArgs() > 1 &&
|
|
!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(1), Set))
|
|
return;
|
|
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLVkBindingAttr(getASTContext(), AL, Binding, Set));
|
|
}
|
|
|
|
void SemaHLSL::handleVkLocationAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t Location;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), Location))
|
|
return;
|
|
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLVkLocationAttr(getASTContext(), AL, Location));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool SemaHLSL::diagnosePositionType(QualType T, const ParsedAttr &AL) {
|
|
const auto *VT = T->getAs<VectorType>();
|
|
if (!T->hasFloatingRepresentation() || (VT && VT->getNumElements() > 4)) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
|
|
<< AL << "float/float1/float2/float3/float4";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SemaHLSL::diagnoseSystemSemanticAttr(Decl *D, const ParsedAttr &AL,
|
|
std::optional<unsigned> Index) {
|
|
std::string SemanticName = AL.getAttrName()->getName().upper();
|
|
|
|
auto *VD = cast<ValueDecl>(D);
|
|
QualType ValueType = VD->getType();
|
|
if (auto *FD = dyn_cast<FunctionDecl>(D))
|
|
ValueType = FD->getReturnType();
|
|
|
|
bool IsOutput = false;
|
|
if (HLSLParamModifierAttr *MA = D->getAttr<HLSLParamModifierAttr>()) {
|
|
if (MA->isOut()) {
|
|
IsOutput = true;
|
|
ValueType = cast<ReferenceType>(ValueType)->getPointeeType();
|
|
}
|
|
}
|
|
|
|
if (SemanticName == "SV_DISPATCHTHREADID") {
|
|
diagnoseInputIDType(ValueType, AL);
|
|
if (IsOutput)
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_output_not_supported) << AL;
|
|
if (Index.has_value())
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_indexing_not_supported) << AL;
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_GROUPINDEX") {
|
|
if (IsOutput)
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_output_not_supported) << AL;
|
|
if (Index.has_value())
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_indexing_not_supported) << AL;
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_GROUPTHREADID") {
|
|
diagnoseInputIDType(ValueType, AL);
|
|
if (IsOutput)
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_output_not_supported) << AL;
|
|
if (Index.has_value())
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_indexing_not_supported) << AL;
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_GROUPID") {
|
|
diagnoseInputIDType(ValueType, AL);
|
|
if (IsOutput)
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_output_not_supported) << AL;
|
|
if (Index.has_value())
|
|
Diag(AL.getLoc(), diag::err_hlsl_semantic_indexing_not_supported) << AL;
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_POSITION") {
|
|
const auto *VT = ValueType->getAs<VectorType>();
|
|
if (!ValueType->hasFloatingRepresentation() ||
|
|
(VT && VT->getNumElements() > 4))
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
|
|
<< AL << "float/float1/float2/float3/float4";
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_VERTEXID") {
|
|
uint64_t SizeInBits = SemaRef.Context.getTypeSize(ValueType);
|
|
if (!ValueType->isUnsignedIntegerType() || SizeInBits != 32)
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type) << AL << "uint";
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
if (SemanticName == "SV_TARGET") {
|
|
const auto *VT = ValueType->getAs<VectorType>();
|
|
if (!ValueType->hasFloatingRepresentation() ||
|
|
(VT && VT->getNumElements() > 4))
|
|
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
|
|
<< AL << "float/float1/float2/float3/float4";
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
return;
|
|
}
|
|
|
|
Diag(AL.getLoc(), diag::err_hlsl_unknown_semantic) << AL;
|
|
}
|
|
|
|
void SemaHLSL::handleSemanticAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t IndexValue(0), ExplicitIndex(0);
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), IndexValue) ||
|
|
!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(1), ExplicitIndex)) {
|
|
assert(0 && "HLSLUnparsedSemantic is expected to have 2 int arguments.");
|
|
}
|
|
assert(IndexValue > 0 ? ExplicitIndex : true);
|
|
std::optional<unsigned> Index =
|
|
ExplicitIndex ? std::optional<unsigned>(IndexValue) : std::nullopt;
|
|
|
|
if (AL.getAttrName()->getName().starts_with_insensitive("SV_"))
|
|
diagnoseSystemSemanticAttr(D, AL, Index);
|
|
else
|
|
D->addAttr(createSemanticAttr<HLSLParsedSemanticAttr>(AL, Index));
|
|
}
|
|
|
|
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) {
|
|
Diag(AL.getLoc(), diag::err_hlsl_invalid_register_or_packoffset);
|
|
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;
|
|
bool HasResourceDimension = 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::HLSLResourceDimension: {
|
|
llvm::dxil::ResourceDimension RD =
|
|
cast<HLSLResourceDimensionAttr>(A)->getDimension();
|
|
if (HasResourceDimension) {
|
|
S.Diag(A->getLocation(), ResAttrs.ResourceDimension == RD
|
|
? diag::warn_duplicate_attribute_exact
|
|
: diag::warn_duplicate_attribute)
|
|
<< A;
|
|
return false;
|
|
}
|
|
ResAttrs.ResourceDimension = RD;
|
|
HasResourceDimension = 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::HLSLIsCounter:
|
|
if (ResAttrs.IsCounter) {
|
|
S.Diag(A->getLocation(), diag::warn_duplicate_attribute_exact) << A;
|
|
return false;
|
|
}
|
|
ResAttrs.IsCounter = 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;
|
|
|
|
AttributeCommonInfo ACI(
|
|
AL.getLoc(), AttributeScopeInfo(AL.getScopeName(), AL.getScopeLoc()),
|
|
AttributeCommonInfo::NoSemaHandlerAttribute,
|
|
{
|
|
AttributeCommonInfo::AS_CXX11, 0, false /*IsAlignas*/,
|
|
false /*IsRegularKeywordAttribute*/
|
|
});
|
|
|
|
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->getIdentifierInfo()->getName();
|
|
SourceLocation ArgLoc = Loc->getLoc();
|
|
|
|
// 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, ACI);
|
|
break;
|
|
}
|
|
|
|
case ParsedAttr::AT_HLSLResourceDimension: {
|
|
StringRef Identifier;
|
|
SourceLocation ArgLoc;
|
|
if (!SemaRef.checkStringLiteralArgumentAttr(AL, 0, Identifier, &ArgLoc))
|
|
return false;
|
|
|
|
// Validate resource dimension value
|
|
llvm::dxil::ResourceDimension RD;
|
|
if (!HLSLResourceDimensionAttr::ConvertStrToResourceDimension(Identifier,
|
|
RD)) {
|
|
Diag(ArgLoc, diag::warn_attribute_type_not_supported)
|
|
<< "ResourceDimension" << Identifier;
|
|
return false;
|
|
}
|
|
A = HLSLResourceDimensionAttr::Create(getASTContext(), RD, ACI);
|
|
break;
|
|
}
|
|
|
|
case ParsedAttr::AT_HLSLROV:
|
|
A = HLSLROVAttr::Create(getASTContext(), ACI);
|
|
break;
|
|
|
|
case ParsedAttr::AT_HLSLRawBuffer:
|
|
A = HLSLRawBufferAttr::Create(getASTContext(), ACI);
|
|
break;
|
|
|
|
case ParsedAttr::AT_HLSLIsCounter:
|
|
A = HLSLIsCounterAttr::Create(getASTContext(), ACI);
|
|
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, ACI);
|
|
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()->getDefinitionOrSelf();
|
|
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))
|
|
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);
|
|
}
|
|
|
|
// return false if the slot count exceeds the limit, true otherwise
|
|
static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &StartSlot,
|
|
const uint64_t &Limit,
|
|
const ResourceClass ResClass,
|
|
ASTContext &Ctx,
|
|
uint64_t ArrayCount = 1) {
|
|
Ty = Ty.getCanonicalType();
|
|
const Type *T = Ty.getTypePtr();
|
|
|
|
// Early exit if already overflowed
|
|
if (StartSlot > Limit)
|
|
return false;
|
|
|
|
// Case 1: array type
|
|
if (const auto *AT = dyn_cast<ArrayType>(T)) {
|
|
uint64_t Count = 1;
|
|
|
|
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
|
|
Count = CAT->getSize().getZExtValue();
|
|
|
|
QualType ElemTy = AT->getElementType();
|
|
return AccumulateHLSLResourceSlots(ElemTy, StartSlot, Limit, ResClass, Ctx,
|
|
ArrayCount * Count);
|
|
}
|
|
|
|
// Case 2: resource leaf
|
|
if (auto ResTy = dyn_cast<HLSLAttributedResourceType>(T)) {
|
|
// First ensure this resource counts towards the corresponding
|
|
// register type limit.
|
|
if (ResTy->getAttrs().ResourceClass != ResClass)
|
|
return true;
|
|
|
|
// Validate highest slot used
|
|
uint64_t EndSlot = StartSlot + ArrayCount - 1;
|
|
if (EndSlot > Limit)
|
|
return false;
|
|
|
|
// Advance SlotCount past the consumed range
|
|
StartSlot = EndSlot + 1;
|
|
return true;
|
|
}
|
|
|
|
// Case 3: struct / record
|
|
if (const auto *RT = dyn_cast<RecordType>(T)) {
|
|
const RecordDecl *RD = RT->getDecl();
|
|
|
|
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
|
|
for (const CXXBaseSpecifier &Base : CXXRD->bases()) {
|
|
if (!AccumulateHLSLResourceSlots(Base.getType(), StartSlot, Limit,
|
|
ResClass, Ctx, ArrayCount))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const FieldDecl *Field : RD->fields()) {
|
|
if (!AccumulateHLSLResourceSlots(Field->getType(), StartSlot, Limit,
|
|
ResClass, Ctx, ArrayCount))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Case 4: everything else
|
|
return true;
|
|
}
|
|
|
|
// return true if there is something invalid, false otherwise
|
|
static bool ValidateRegisterNumber(uint64_t SlotNum, Decl *TheDecl,
|
|
ASTContext &Ctx, RegisterType RegTy) {
|
|
const uint64_t Limit = UINT32_MAX;
|
|
if (SlotNum > Limit)
|
|
return true;
|
|
|
|
// after verifying the number doesn't exceed uint32max, we don't need
|
|
// to look further into c or i register types
|
|
if (RegTy == RegisterType::C || RegTy == RegisterType::I)
|
|
return false;
|
|
|
|
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
|
|
uint64_t BaseSlot = SlotNum;
|
|
|
|
if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit,
|
|
getResourceClass(RegTy), Ctx))
|
|
return true;
|
|
|
|
// After AccumulateHLSLResourceSlots runs, SlotNum is now
|
|
// the first free slot; last used was SlotNum - 1
|
|
return (BaseSlot > Limit);
|
|
}
|
|
// handle the cbuffer/tbuffer case
|
|
if (isa<HLSLBufferDecl>(TheDecl))
|
|
// resources cannot be put within a cbuffer, so no need
|
|
// to analyze the structure since the register number
|
|
// won't be pushed any higher.
|
|
return (SlotNum > Limit);
|
|
|
|
// we don't expect any other decl type, so fail
|
|
llvm_unreachable("unexpected decl type");
|
|
}
|
|
|
|
void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
|
|
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
|
|
QualType Ty = VD->getType();
|
|
if (const auto *IAT = dyn_cast<IncompleteArrayType>(Ty))
|
|
Ty = IAT->getElementType();
|
|
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(), Ty,
|
|
diag::err_incomplete_type))
|
|
return;
|
|
}
|
|
|
|
StringRef Slot = "";
|
|
StringRef Space = "";
|
|
SourceLocation SlotLoc, SpaceLoc;
|
|
|
|
if (!AL.isArgIdent(0)) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
|
<< AL << AANT_ArgumentIdentifier;
|
|
return;
|
|
}
|
|
IdentifierLoc *Loc = AL.getArgAsIdent(0);
|
|
|
|
if (AL.getNumArgs() == 2) {
|
|
Slot = Loc->getIdentifierInfo()->getName();
|
|
SlotLoc = Loc->getLoc();
|
|
if (!AL.isArgIdent(1)) {
|
|
Diag(AL.getLoc(), diag::err_attribute_argument_type)
|
|
<< AL << AANT_ArgumentIdentifier;
|
|
return;
|
|
}
|
|
Loc = AL.getArgAsIdent(1);
|
|
Space = Loc->getIdentifierInfo()->getName();
|
|
SpaceLoc = Loc->getLoc();
|
|
} else {
|
|
StringRef Str = Loc->getIdentifierInfo()->getName();
|
|
if (Str.starts_with("space")) {
|
|
Space = Str;
|
|
SpaceLoc = Loc->getLoc();
|
|
} else {
|
|
Slot = Str;
|
|
SlotLoc = Loc->getLoc();
|
|
Space = "space0";
|
|
}
|
|
}
|
|
|
|
RegisterType RegType = RegisterType::SRV;
|
|
std::optional<unsigned> SlotNum;
|
|
unsigned SpaceNum = 0;
|
|
|
|
// Validate slot
|
|
if (!Slot.empty()) {
|
|
if (!convertToRegisterType(Slot, &RegType)) {
|
|
Diag(SlotLoc, diag::err_hlsl_binding_type_invalid) << Slot.substr(0, 1);
|
|
return;
|
|
}
|
|
if (RegType == RegisterType::I) {
|
|
Diag(SlotLoc, diag::warn_hlsl_deprecated_register_type_i);
|
|
return;
|
|
}
|
|
const StringRef SlotNumStr = Slot.substr(1);
|
|
|
|
uint64_t N;
|
|
|
|
// validate that the slot number is a non-empty number
|
|
if (SlotNumStr.getAsInteger(10, N)) {
|
|
Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
|
|
return;
|
|
}
|
|
|
|
// Validate register number. It should not exceed UINT32_MAX,
|
|
// including if the resource type is an array that starts
|
|
// before UINT32_MAX, but ends afterwards.
|
|
if (ValidateRegisterNumber(N, TheDecl, getASTContext(), RegType)) {
|
|
Diag(SlotLoc, diag::err_hlsl_register_number_too_large);
|
|
return;
|
|
}
|
|
|
|
// the slot number has been validated and does not exceed UINT32_MAX
|
|
SlotNum = (unsigned)N;
|
|
}
|
|
|
|
// Validate space
|
|
if (!Space.starts_with("space")) {
|
|
Diag(SpaceLoc, diag::err_hlsl_expected_space) << Space;
|
|
return;
|
|
}
|
|
StringRef SpaceNumStr = Space.substr(5);
|
|
if (SpaceNumStr.getAsInteger(10, SpaceNum)) {
|
|
Diag(SpaceLoc, diag::err_hlsl_expected_space) << Space;
|
|
return;
|
|
}
|
|
|
|
// If we have slot, diagnose it is the right register type for the decl
|
|
if (SlotNum.has_value())
|
|
if (!DiagnoseHLSLRegisterAttribute(SemaRef, SlotLoc, TheDecl, RegType,
|
|
!SpaceLoc.isInvalid()))
|
|
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) {
|
|
const 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) {
|
|
|
|
const 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);
|
|
addImplicitBindingAttrToDecl(SemaRef, DefaultCBuffer, RegisterType::CBuffer,
|
|
getNextImplicitBindingOrderID());
|
|
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->hasRegisterSlot() &&
|
|
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);
|
|
}
|
|
|
|
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 CheckAllArgTypesAreCorrect(
|
|
Sema *S, CallExpr *TheCall,
|
|
llvm::function_ref<bool(Sema *S, SourceLocation Loc, int ArgOrdinal,
|
|
clang::QualType PassedType)>
|
|
Check) {
|
|
for (unsigned I = 0; I < TheCall->getNumArgs(); ++I) {
|
|
Expr *Arg = TheCall->getArg(I);
|
|
if (Check(S, Arg->getBeginLoc(), I + 1, Arg->getType()))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool CheckFloatRepresentation(Sema *S, SourceLocation Loc,
|
|
int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
clang::QualType BaseType =
|
|
PassedType->isVectorType()
|
|
? PassedType->castAs<clang::VectorType>()->getElementType()
|
|
: PassedType;
|
|
if (!BaseType->isFloat32Type())
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* scalar or vector of */ 5 << /* no int */ 0
|
|
<< /* float */ 1 << PassedType;
|
|
return false;
|
|
}
|
|
|
|
static bool CheckFloatOrHalfRepresentation(Sema *S, SourceLocation Loc,
|
|
int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
clang::QualType BaseType =
|
|
PassedType->isVectorType()
|
|
? PassedType->castAs<clang::VectorType>()->getElementType()
|
|
: PassedType;
|
|
if (!BaseType->isHalfType() && !BaseType->isFloat32Type())
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* scalar or vector of */ 5 << /* no int */ 0
|
|
<< /* half or float */ 2 << PassedType;
|
|
return false;
|
|
}
|
|
|
|
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, SourceLocation Loc, int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
const auto *VecTy = PassedType->getAs<VectorType>();
|
|
if (!VecTy)
|
|
return false;
|
|
|
|
if (VecTy->getElementType()->isDoubleType())
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* scalar */ 1 << /* no int */ 0 << /* fp */ 1
|
|
<< PassedType;
|
|
return false;
|
|
}
|
|
|
|
static bool CheckFloatingOrIntRepresentation(Sema *S, SourceLocation Loc,
|
|
int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
if (!PassedType->hasIntegerRepresentation() &&
|
|
!PassedType->hasFloatingRepresentation())
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* scalar or vector of */ 5 << /* integer */ 1
|
|
<< /* fp */ 1 << PassedType;
|
|
return false;
|
|
}
|
|
|
|
static bool CheckUnsignedIntVecRepresentation(Sema *S, SourceLocation Loc,
|
|
int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
if (auto *VecTy = PassedType->getAs<VectorType>())
|
|
if (VecTy->getElementType()->isUnsignedIntegerType())
|
|
return false;
|
|
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* vector of */ 4 << /* uint */ 3 << /* no fp */ 0
|
|
<< PassedType;
|
|
}
|
|
|
|
// checks for unsigned ints of all sizes
|
|
static bool CheckUnsignedIntRepresentation(Sema *S, SourceLocation Loc,
|
|
int ArgOrdinal,
|
|
clang::QualType PassedType) {
|
|
if (!PassedType->hasUnsignedIntegerRepresentation())
|
|
return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
|
|
<< ArgOrdinal << /* scalar or vector of */ 5 << /* unsigned int */ 3
|
|
<< /* no fp */ 0 << PassedType;
|
|
return false;
|
|
}
|
|
|
|
static bool CheckExpectedBitWidth(Sema *S, CallExpr *TheCall,
|
|
unsigned ArgOrdinal, unsigned Width) {
|
|
QualType ArgTy = TheCall->getArg(0)->getType();
|
|
if (auto *VTy = ArgTy->getAs<VectorType>())
|
|
ArgTy = VTy->getElementType();
|
|
// ensure arg type has expected bit width
|
|
uint64_t ElementBitCount =
|
|
S->getASTContext().getTypeSizeInChars(ArgTy).getQuantity() * 8;
|
|
if (ElementBitCount != Width) {
|
|
S->Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_integer_incorrect_bit_count)
|
|
<< Width << ElementBitCount;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void SetElementTypeAsReturnType(Sema *S, CallExpr *TheCall,
|
|
QualType ReturnType) {
|
|
auto *VecTyA = TheCall->getArg(0)->getType()->getAs<VectorType>();
|
|
if (VecTyA)
|
|
ReturnType =
|
|
S->Context.getExtVectorType(ReturnType, VecTyA->getNumElements());
|
|
|
|
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 CheckScalarOrVectorOrMatrix(Sema *S, CallExpr *TheCall,
|
|
QualType Scalar, unsigned ArgIndex) {
|
|
assert(TheCall->getNumArgs() > ArgIndex);
|
|
|
|
Expr *Arg = TheCall->getArg(ArgIndex);
|
|
QualType ArgType = Arg->getType();
|
|
|
|
// Scalar: T
|
|
if (S->Context.hasSameUnqualifiedType(ArgType, Scalar))
|
|
return false;
|
|
|
|
// Vector: vector<T>
|
|
if (const auto *VTy = ArgType->getAs<VectorType>()) {
|
|
if (S->Context.hasSameUnqualifiedType(VTy->getElementType(), Scalar))
|
|
return false;
|
|
}
|
|
|
|
// Matrix: ConstantMatrixType with element type T
|
|
if (const auto *MTy = ArgType->getAs<ConstantMatrixType>()) {
|
|
if (S->Context.hasSameUnqualifiedType(MTy->getElementType(), Scalar))
|
|
return false;
|
|
}
|
|
|
|
// Not a scalar/vector/matrix-of-scalar
|
|
S->Diag(Arg->getBeginLoc(),
|
|
diag::err_typecheck_expect_scalar_or_vector_or_matrix)
|
|
<< ArgType << Scalar;
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Check that the argument is not a bool or vector<bool>
|
|
// Returns true on error
|
|
static bool CheckNotBoolScalarOrVector(Sema *S, CallExpr *TheCall,
|
|
unsigned ArgIndex) {
|
|
QualType BoolType = S->getASTContext().BoolTy;
|
|
assert(ArgIndex < TheCall->getNumArgs());
|
|
QualType ArgType = TheCall->getArg(ArgIndex)->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 CheckWaveActive(Sema *S, CallExpr *TheCall) {
|
|
if (CheckNotBoolScalarOrVector(S, TheCall, 0))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool CheckWavePrefix(Sema *S, CallExpr *TheCall) {
|
|
if (CheckNotBoolScalarOrVector(S, TheCall, 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 CheckIndexType(Sema *S, CallExpr *TheCall, unsigned IndexArgIndex) {
|
|
assert(TheCall->getNumArgs() > IndexArgIndex && "Index argument missing");
|
|
QualType ArgType = TheCall->getArg(IndexArgIndex)->getType();
|
|
QualType IndexTy = ArgType;
|
|
unsigned int ActualDim = 1;
|
|
if (const auto *VTy = IndexTy->getAs<VectorType>()) {
|
|
ActualDim = VTy->getNumElements();
|
|
IndexTy = VTy->getElementType();
|
|
}
|
|
if (!IndexTy->isIntegerType()) {
|
|
S->Diag(TheCall->getArg(IndexArgIndex)->getBeginLoc(),
|
|
diag::err_typecheck_expect_int)
|
|
<< ArgType;
|
|
return true;
|
|
}
|
|
|
|
QualType ResourceArgTy = TheCall->getArg(0)->getType();
|
|
const HLSLAttributedResourceType *ResTy =
|
|
ResourceArgTy.getTypePtr()->getAs<HLSLAttributedResourceType>();
|
|
assert(ResTy && "Resource argument must be a resource");
|
|
HLSLAttributedResourceType::Attributes ResAttrs = ResTy->getAttrs();
|
|
|
|
unsigned int ExpectedDim = 1;
|
|
if (ResAttrs.ResourceDimension != llvm::dxil::ResourceDimension::Unknown)
|
|
ExpectedDim = getResourceDimensions(ResAttrs.ResourceDimension);
|
|
|
|
if (ActualDim != ExpectedDim) {
|
|
S->Diag(TheCall->getArg(IndexArgIndex)->getBeginLoc(),
|
|
diag::err_hlsl_builtin_resource_coordinate_dimension_mismatch)
|
|
<< cast<NamedDecl>(TheCall->getCalleeDecl()) << ExpectedDim
|
|
<< ActualDim;
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static bool CheckVectorElementCount(Sema *S, QualType PassedType,
|
|
QualType BaseType, unsigned ExpectedCount,
|
|
SourceLocation Loc) {
|
|
unsigned PassedCount = 1;
|
|
if (const auto *VecTy = PassedType->getAs<VectorType>())
|
|
PassedCount = VecTy->getNumElements();
|
|
|
|
if (PassedCount != ExpectedCount) {
|
|
QualType ExpectedType =
|
|
S->Context.getExtVectorType(BaseType, ExpectedCount);
|
|
S->Diag(Loc, diag::err_typecheck_convert_incompatible)
|
|
<< PassedType << ExpectedType << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
enum class SampleKind { Sample, Bias, Grad, Level, Cmp, CmpLevelZero };
|
|
|
|
static bool CheckTextureSamplerAndLocation(Sema &S, CallExpr *TheCall) {
|
|
// Check the texture handle.
|
|
if (CheckResourceHandle(&S, TheCall, 0,
|
|
[](const HLSLAttributedResourceType *ResType) {
|
|
return ResType->getAttrs().ResourceDimension ==
|
|
llvm::dxil::ResourceDimension::Unknown;
|
|
}))
|
|
return true;
|
|
|
|
// Check the sampler handle.
|
|
if (CheckResourceHandle(&S, TheCall, 1,
|
|
[](const HLSLAttributedResourceType *ResType) {
|
|
return ResType->getAttrs().ResourceClass !=
|
|
llvm::hlsl::ResourceClass::Sampler;
|
|
}))
|
|
return true;
|
|
|
|
auto *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
|
|
// Check the location.
|
|
unsigned ExpectedDim =
|
|
getResourceDimensions(ResourceTy->getAttrs().ResourceDimension);
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(2)->getType(),
|
|
S.Context.FloatTy, ExpectedDim,
|
|
TheCall->getBeginLoc()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool CheckGatherBuiltin(Sema &S, CallExpr *TheCall, bool IsCmp) {
|
|
if (S.checkArgCountRange(TheCall, IsCmp ? 5 : 4, IsCmp ? 6 : 5))
|
|
return true;
|
|
|
|
if (CheckTextureSamplerAndLocation(S, TheCall))
|
|
return true;
|
|
|
|
unsigned NextIdx = 3;
|
|
if (IsCmp) {
|
|
// Check the compare value.
|
|
QualType CmpTy = TheCall->getArg(NextIdx)->getType();
|
|
if (!CmpTy->isFloatingType() || CmpTy->isVectorType()) {
|
|
S.Diag(TheCall->getArg(NextIdx)->getBeginLoc(),
|
|
diag::err_typecheck_convert_incompatible)
|
|
<< CmpTy << S.Context.FloatTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
NextIdx++;
|
|
}
|
|
|
|
// Check the component operand.
|
|
Expr *ComponentArg = TheCall->getArg(NextIdx);
|
|
QualType ComponentTy = ComponentArg->getType();
|
|
if (!ComponentTy->isIntegerType() || ComponentTy->isVectorType()) {
|
|
S.Diag(ComponentArg->getBeginLoc(),
|
|
diag::err_typecheck_convert_incompatible)
|
|
<< ComponentTy << S.Context.UnsignedIntTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
|
|
// GatherCmp operations on Vulkan target must use component 0 (Red).
|
|
if (IsCmp && S.getASTContext().getTargetInfo().getTriple().isSPIRV()) {
|
|
std::optional<llvm::APSInt> ComponentOpt =
|
|
ComponentArg->getIntegerConstantExpr(S.getASTContext());
|
|
if (ComponentOpt) {
|
|
int64_t ComponentVal = ComponentOpt->getSExtValue();
|
|
if (ComponentVal != 0) {
|
|
// Issue an error if the component is not 0 (Red).
|
|
// 0 -> Red, 1 -> Green, 2 -> Blue, 3 -> Alpha
|
|
assert(ComponentVal >= 0 && ComponentVal <= 3 &&
|
|
"The component is not in the expected range.");
|
|
S.Diag(ComponentArg->getBeginLoc(),
|
|
diag::err_hlsl_gathercmp_invalid_component)
|
|
<< ComponentVal;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
NextIdx++;
|
|
|
|
// Check the offset operand.
|
|
const HLSLAttributedResourceType *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
if (TheCall->getNumArgs() > NextIdx) {
|
|
unsigned ExpectedDim =
|
|
getResourceDimensions(ResourceTy->getAttrs().ResourceDimension);
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(NextIdx)->getType(),
|
|
S.Context.IntTy, ExpectedDim,
|
|
TheCall->getArg(NextIdx)->getBeginLoc()))
|
|
return true;
|
|
NextIdx++;
|
|
}
|
|
|
|
assert(ResourceTy->hasContainedType() &&
|
|
"Expecting a contained type for resource with a dimension "
|
|
"attribute.");
|
|
QualType ReturnType = ResourceTy->getContainedType();
|
|
|
|
if (IsCmp) {
|
|
if (!ReturnType->hasFloatingRepresentation()) {
|
|
S.Diag(TheCall->getBeginLoc(), diag::err_hlsl_samplecmp_requires_float);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (const auto *VecTy = ReturnType->getAs<VectorType>())
|
|
ReturnType = VecTy->getElementType();
|
|
ReturnType = S.Context.getExtVectorType(ReturnType, 4);
|
|
|
|
TheCall->setType(ReturnType);
|
|
|
|
return false;
|
|
}
|
|
static bool CheckLoadLevelBuiltin(Sema &S, CallExpr *TheCall) {
|
|
if (S.checkArgCountRange(TheCall, 2, 3))
|
|
return true;
|
|
|
|
// Check the texture handle.
|
|
if (CheckResourceHandle(&S, TheCall, 0,
|
|
[](const HLSLAttributedResourceType *ResType) {
|
|
return ResType->getAttrs().ResourceDimension ==
|
|
llvm::dxil::ResourceDimension::Unknown;
|
|
}))
|
|
return true;
|
|
|
|
auto *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
|
|
// Check the location + lod (int3 for Texture2D).
|
|
unsigned ExpectedDim =
|
|
getResourceDimensions(ResourceTy->getAttrs().ResourceDimension);
|
|
QualType CoordLODTy = TheCall->getArg(1)->getType();
|
|
if (CheckVectorElementCount(&S, CoordLODTy, S.Context.IntTy, ExpectedDim + 1,
|
|
TheCall->getArg(1)->getBeginLoc()))
|
|
return true;
|
|
|
|
QualType EltTy = CoordLODTy;
|
|
if (const auto *VTy = EltTy->getAs<VectorType>())
|
|
EltTy = VTy->getElementType();
|
|
if (!EltTy->isIntegerType()) {
|
|
S.Diag(TheCall->getArg(1)->getBeginLoc(), diag::err_typecheck_expect_int)
|
|
<< CoordLODTy;
|
|
return true;
|
|
}
|
|
|
|
// Check the offset operand.
|
|
if (TheCall->getNumArgs() > 2) {
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(2)->getType(),
|
|
S.Context.IntTy, ExpectedDim,
|
|
TheCall->getArg(2)->getBeginLoc()))
|
|
return true;
|
|
}
|
|
|
|
TheCall->setType(ResourceTy->getContainedType());
|
|
return false;
|
|
}
|
|
|
|
static bool CheckSamplingBuiltin(Sema &S, CallExpr *TheCall, SampleKind Kind) {
|
|
unsigned MinArgs, MaxArgs;
|
|
if (Kind == SampleKind::Sample) {
|
|
MinArgs = 3;
|
|
MaxArgs = 5;
|
|
} else if (Kind == SampleKind::Bias) {
|
|
MinArgs = 4;
|
|
MaxArgs = 6;
|
|
} else if (Kind == SampleKind::Grad) {
|
|
MinArgs = 5;
|
|
MaxArgs = 7;
|
|
} else if (Kind == SampleKind::Level) {
|
|
MinArgs = 4;
|
|
MaxArgs = 5;
|
|
} else if (Kind == SampleKind::Cmp) {
|
|
MinArgs = 4;
|
|
MaxArgs = 6;
|
|
} else {
|
|
assert(Kind == SampleKind::CmpLevelZero);
|
|
MinArgs = 4;
|
|
MaxArgs = 5;
|
|
}
|
|
|
|
if (S.checkArgCountRange(TheCall, MinArgs, MaxArgs))
|
|
return true;
|
|
|
|
if (CheckTextureSamplerAndLocation(S, TheCall))
|
|
return true;
|
|
|
|
const HLSLAttributedResourceType *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
unsigned ExpectedDim =
|
|
getResourceDimensions(ResourceTy->getAttrs().ResourceDimension);
|
|
|
|
unsigned NextIdx = 3;
|
|
if (Kind == SampleKind::Bias || Kind == SampleKind::Level ||
|
|
Kind == SampleKind::Cmp || Kind == SampleKind::CmpLevelZero) {
|
|
// Check the bias, lod level, or compare value, depending on the kind.
|
|
// All of them must be a scalar float value.
|
|
QualType BiasOrLODOrCmpTy = TheCall->getArg(NextIdx)->getType();
|
|
if (!BiasOrLODOrCmpTy->isFloatingType() ||
|
|
BiasOrLODOrCmpTy->isVectorType()) {
|
|
S.Diag(TheCall->getArg(NextIdx)->getBeginLoc(),
|
|
diag::err_typecheck_convert_incompatible)
|
|
<< BiasOrLODOrCmpTy << S.Context.FloatTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
NextIdx++;
|
|
} else if (Kind == SampleKind::Grad) {
|
|
// Check the DDX operand.
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(NextIdx)->getType(),
|
|
S.Context.FloatTy, ExpectedDim,
|
|
TheCall->getArg(NextIdx)->getBeginLoc()))
|
|
return true;
|
|
|
|
// Check the DDY operand.
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(NextIdx + 1)->getType(),
|
|
S.Context.FloatTy, ExpectedDim,
|
|
TheCall->getArg(NextIdx + 1)->getBeginLoc()))
|
|
return true;
|
|
NextIdx += 2;
|
|
}
|
|
|
|
// Check the offset operand.
|
|
if (TheCall->getNumArgs() > NextIdx) {
|
|
if (CheckVectorElementCount(&S, TheCall->getArg(NextIdx)->getType(),
|
|
S.Context.IntTy, ExpectedDim,
|
|
TheCall->getArg(NextIdx)->getBeginLoc()))
|
|
return true;
|
|
NextIdx++;
|
|
}
|
|
|
|
// Check the clamp operand.
|
|
if (Kind != SampleKind::Level && Kind != SampleKind::CmpLevelZero &&
|
|
TheCall->getNumArgs() > NextIdx) {
|
|
QualType ClampTy = TheCall->getArg(NextIdx)->getType();
|
|
if (!ClampTy->isFloatingType() || ClampTy->isVectorType()) {
|
|
S.Diag(TheCall->getArg(NextIdx)->getBeginLoc(),
|
|
diag::err_typecheck_convert_incompatible)
|
|
<< ClampTy << S.Context.FloatTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
assert(ResourceTy->hasContainedType() &&
|
|
"Expecting a contained type for resource with a dimension "
|
|
"attribute.");
|
|
QualType ReturnType = ResourceTy->getContainedType();
|
|
if (Kind == SampleKind::Cmp || Kind == SampleKind::CmpLevelZero) {
|
|
if (!ReturnType->hasFloatingRepresentation()) {
|
|
S.Diag(TheCall->getBeginLoc(), diag::err_hlsl_samplecmp_requires_float);
|
|
return true;
|
|
}
|
|
ReturnType = S.Context.FloatTy;
|
|
}
|
|
TheCall->setType(ReturnType);
|
|
|
|
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 (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckUnsignedIntVecRepresentation))
|
|
return true;
|
|
|
|
// ensure arg integers are 32-bits
|
|
if (CheckExpectedBitWidth(&SemaRef, TheCall, 0, 32))
|
|
return true;
|
|
|
|
// ensure both args are vectors of total bit size of a multiple of 64
|
|
auto *VTy = TheCall->getArg(0)->getType()->getAs<VectorType>();
|
|
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 * 32;
|
|
return true;
|
|
}
|
|
|
|
// ensure first arg and second arg have the same type
|
|
if (CheckAllArgsHaveSameType(&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_resource_getpointer: {
|
|
if (SemaRef.checkArgCount(TheCall, 2) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0) ||
|
|
CheckIndexType(&SemaRef, TheCall, 1))
|
|
return true;
|
|
|
|
auto *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
QualType ContainedTy = ResourceTy->getContainedType();
|
|
auto ReturnType =
|
|
SemaRef.Context.getAddrSpaceQualType(ContainedTy, LangAS::hlsl_device);
|
|
ReturnType = SemaRef.Context.getPointerType(ReturnType);
|
|
TheCall->setType(ReturnType);
|
|
TheCall->setValueKind(VK_LValue);
|
|
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_getpointer_typed: {
|
|
if (SemaRef.checkArgCount(TheCall, 3) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0) ||
|
|
CheckIndexType(&SemaRef, TheCall, 1))
|
|
return true;
|
|
|
|
QualType ElementTy = TheCall->getArg(2)->getType();
|
|
assert(ElementTy->isPointerType() &&
|
|
"expected pointer type for second argument");
|
|
ElementTy = ElementTy->getPointeeType();
|
|
|
|
// Reject array types
|
|
if (ElementTy->isArrayType())
|
|
return SemaRef.Diag(
|
|
cast<FunctionDecl>(SemaRef.CurContext)->getPointOfInstantiation(),
|
|
diag::err_invalid_use_of_array_type);
|
|
|
|
auto ReturnType =
|
|
SemaRef.Context.getAddrSpaceQualType(ElementTy, LangAS::hlsl_device);
|
|
ReturnType = SemaRef.Context.getPointerType(ReturnType);
|
|
TheCall->setType(ReturnType);
|
|
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_load_with_status: {
|
|
if (SemaRef.checkArgCount(TheCall, 3) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1),
|
|
SemaRef.getASTContext().UnsignedIntTy) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(2),
|
|
SemaRef.getASTContext().UnsignedIntTy) ||
|
|
CheckModifiableLValue(&SemaRef, TheCall, 2))
|
|
return true;
|
|
|
|
auto *ResourceTy =
|
|
TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
|
|
QualType ReturnType = ResourceTy->getContainedType();
|
|
TheCall->setType(ReturnType);
|
|
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_load_with_status_typed: {
|
|
if (SemaRef.checkArgCount(TheCall, 4) ||
|
|
CheckResourceHandle(&SemaRef, TheCall, 0) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(1),
|
|
SemaRef.getASTContext().UnsignedIntTy) ||
|
|
CheckArgTypeMatches(&SemaRef, TheCall->getArg(2),
|
|
SemaRef.getASTContext().UnsignedIntTy) ||
|
|
CheckModifiableLValue(&SemaRef, TheCall, 2))
|
|
return true;
|
|
|
|
QualType ReturnType = TheCall->getArg(3)->getType();
|
|
assert(ReturnType->isPointerType() &&
|
|
"expected pointer type for second argument");
|
|
ReturnType = ReturnType->getPointeeType();
|
|
|
|
// Reject array types
|
|
if (ReturnType->isArrayType())
|
|
return SemaRef.Diag(
|
|
cast<FunctionDecl>(SemaRef.CurContext)->getPointOfInstantiation(),
|
|
diag::err_invalid_use_of_array_type);
|
|
|
|
TheCall->setType(ReturnType);
|
|
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_load_level:
|
|
return CheckLoadLevelBuiltin(SemaRef, TheCall);
|
|
case Builtin::BI__builtin_hlsl_resource_sample:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Sample);
|
|
case Builtin::BI__builtin_hlsl_resource_sample_bias:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Bias);
|
|
case Builtin::BI__builtin_hlsl_resource_sample_grad:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Grad);
|
|
case Builtin::BI__builtin_hlsl_resource_sample_level:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Level);
|
|
case Builtin::BI__builtin_hlsl_resource_sample_cmp:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::Cmp);
|
|
case Builtin::BI__builtin_hlsl_resource_sample_cmp_level_zero:
|
|
return CheckSamplingBuiltin(SemaRef, TheCall, SampleKind::CmpLevelZero);
|
|
case Builtin::BI__builtin_hlsl_resource_gather:
|
|
return CheckGatherBuiltin(SemaRef, TheCall, /*IsCmp=*/false);
|
|
case Builtin::BI__builtin_hlsl_resource_gather_cmp:
|
|
return CheckGatherBuiltin(SemaRef, TheCall, /*IsCmp=*/true);
|
|
case Builtin::BI__builtin_hlsl_resource_uninitializedhandle: {
|
|
assert(TheCall->getNumArgs() == 1 && "expected 1 arg");
|
|
// Update return type to be the attributed resource type from arg0.
|
|
QualType ResourceTy = TheCall->getArg(0)->getType();
|
|
TheCall->setType(ResourceTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_handlefrombinding: {
|
|
assert(TheCall->getNumArgs() == 6 && "expected 6 args");
|
|
// Update return type to be the attributed resource type from arg0.
|
|
QualType ResourceTy = TheCall->getArg(0)->getType();
|
|
TheCall->setType(ResourceTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_handlefromimplicitbinding: {
|
|
assert(TheCall->getNumArgs() == 6 && "expected 6 args");
|
|
// Update return type to be the attributed resource type from arg0.
|
|
QualType ResourceTy = TheCall->getArg(0)->getType();
|
|
TheCall->setType(ResourceTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_resource_counterhandlefromimplicitbinding: {
|
|
assert(TheCall->getNumArgs() == 3 && "expected 3 args");
|
|
ASTContext &AST = SemaRef.getASTContext();
|
|
QualType MainHandleTy = TheCall->getArg(0)->getType();
|
|
auto *MainResType = MainHandleTy->getAs<HLSLAttributedResourceType>();
|
|
auto MainAttrs = MainResType->getAttrs();
|
|
assert(!MainAttrs.IsCounter && "cannot create a counter from a counter");
|
|
MainAttrs.IsCounter = true;
|
|
QualType CounterHandleTy = AST.getHLSLAttributedResourceType(
|
|
MainResType->getWrappedType(), MainResType->getContainedType(),
|
|
MainAttrs);
|
|
// Update return type to be the attributed resource type from arg0
|
|
// with added IsCounter flag.
|
|
TheCall->setType(CounterHandleTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_and:
|
|
case Builtin::BI__builtin_hlsl_or: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckScalarOrVectorOrMatrix(&SemaRef, TheCall, getASTContext().BoolTy,
|
|
0))
|
|
return true;
|
|
if (CheckAllArgsHaveSameType(&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_all:
|
|
case Builtin::BI__builtin_hlsl_any: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_asdouble: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckScalarOrVector(
|
|
&SemaRef, TheCall,
|
|
/*only check for uint*/ SemaRef.Context.UnsignedIntTy,
|
|
/* arg index */ 0))
|
|
return true;
|
|
if (CheckScalarOrVector(
|
|
&SemaRef, TheCall,
|
|
/*only check for uint*/ SemaRef.Context.UnsignedIntTy,
|
|
/* arg index */ 1))
|
|
return true;
|
|
if (CheckAllArgsHaveSameType(&SemaRef, TheCall))
|
|
return true;
|
|
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().DoubleTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_clamp: {
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(
|
|
TheCall, /*ArgTyRestr=*/
|
|
Sema::EltwiseBuiltinArgTyRestriction::None))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_dot: {
|
|
// arg count is checked by BuiltinVectorToScalarMath
|
|
if (SemaRef.BuiltinVectorToScalarMath(TheCall))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall, CheckNoDoubleVectors))
|
|
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.getExtVectorType(ResTy, VecTy->getNumElements());
|
|
}
|
|
|
|
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 (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (!TheCall->getArg(0)
|
|
->getType()
|
|
->hasFloatingRepresentation()) // half or float or double
|
|
return SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_builtin_invalid_arg_type)
|
|
<< /* ordinal */ 1 << /* scalar or vector */ 5 << /* no int */ 0
|
|
<< /* fp */ 1 << TheCall->getArg(0)->getType();
|
|
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:
|
|
case Builtin::BI__builtin_hlsl_elementwise_ddx_coarse:
|
|
case Builtin::BI__builtin_hlsl_elementwise_ddy_coarse:
|
|
case Builtin::BI__builtin_hlsl_elementwise_ddx_fine:
|
|
case Builtin::BI__builtin_hlsl_elementwise_ddy_fine: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
return true;
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_isinf:
|
|
case Builtin::BI__builtin_hlsl_elementwise_isnan: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
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 (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
return true;
|
|
if (CheckAllArgsHaveSameType(&SemaRef, TheCall))
|
|
return true;
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(TheCall))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_mad: {
|
|
if (SemaRef.BuiltinElementwiseTernaryMath(
|
|
TheCall, /*ArgTyRestr=*/
|
|
Sema::EltwiseBuiltinArgTyRestriction::None))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_mul: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
|
|
Expr *Arg0 = TheCall->getArg(0);
|
|
Expr *Arg1 = TheCall->getArg(1);
|
|
QualType Ty0 = Arg0->getType();
|
|
QualType Ty1 = Arg1->getType();
|
|
|
|
auto getElemType = [](QualType T) -> QualType {
|
|
if (const auto *VTy = T->getAs<VectorType>())
|
|
return VTy->getElementType();
|
|
if (const auto *MTy = T->getAs<ConstantMatrixType>())
|
|
return MTy->getElementType();
|
|
return T;
|
|
};
|
|
|
|
QualType EltTy0 = getElemType(Ty0);
|
|
|
|
bool IsVec0 = Ty0->isVectorType();
|
|
bool IsMat0 = Ty0->isConstantMatrixType();
|
|
bool IsVec1 = Ty1->isVectorType();
|
|
bool IsMat1 = Ty1->isConstantMatrixType();
|
|
|
|
QualType RetTy;
|
|
|
|
if (IsVec0 && IsMat1) {
|
|
auto *MatTy = Ty1->castAs<ConstantMatrixType>();
|
|
RetTy = getASTContext().getExtVectorType(EltTy0, MatTy->getNumColumns());
|
|
} else if (IsMat0 && IsVec1) {
|
|
auto *MatTy = Ty0->castAs<ConstantMatrixType>();
|
|
RetTy = getASTContext().getExtVectorType(EltTy0, MatTy->getNumRows());
|
|
} else {
|
|
assert(IsMat0 && IsMat1);
|
|
auto *MatTy0 = Ty0->castAs<ConstantMatrixType>();
|
|
auto *MatTy1 = Ty1->castAs<ConstantMatrixType>();
|
|
RetTy = getASTContext().getConstantMatrixType(
|
|
EltTy0, MatTy0->getNumRows(), MatTy1->getNumColumns());
|
|
}
|
|
|
|
TheCall->setType(RetTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_normalize: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
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_transpose: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
Expr *Arg = TheCall->getArg(0);
|
|
QualType ArgTy = Arg->getType();
|
|
|
|
const auto *MatTy = ArgTy->getAs<ConstantMatrixType>();
|
|
if (!MatTy) {
|
|
SemaRef.Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_type)
|
|
<< 1 << /* matrix */ 3 << /* no int */ 0 << /* no fp */ 0 << ArgTy;
|
|
return true;
|
|
}
|
|
|
|
QualType RetTy = getASTContext().getConstantMatrixType(
|
|
MatTy->getElementType(), MatTy->getNumColumns(), MatTy->getNumRows());
|
|
TheCall->setType(RetTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_sign: {
|
|
if (SemaRef.PrepareBuiltinElementwiseMathOneArgCall(TheCall))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatingOrIntRepresentation))
|
|
return true;
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().IntTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_step: {
|
|
if (SemaRef.checkArgCount(TheCall, 2))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
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_all_equal: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
// Ensure input expr type is a scalar/vector
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
|
|
QualType InputTy = TheCall->getArg(0)->getType();
|
|
ASTContext &Ctx = getASTContext();
|
|
|
|
QualType RetTy;
|
|
|
|
// If vector, construct bool vector of same size
|
|
if (const auto *VecTy = InputTy->getAs<ExtVectorType>()) {
|
|
unsigned NumElts = VecTy->getNumElements();
|
|
RetTy = Ctx.getExtVectorType(Ctx.BoolTy, NumElts);
|
|
} else {
|
|
// Scalar case
|
|
RetTy = Ctx.BoolTy;
|
|
}
|
|
|
|
TheCall->setType(RetTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_active_max:
|
|
case Builtin::BI__builtin_hlsl_wave_active_min:
|
|
case Builtin::BI__builtin_hlsl_wave_active_sum:
|
|
case Builtin::BI__builtin_hlsl_wave_active_product: {
|
|
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;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_active_bit_or:
|
|
case Builtin::BI__builtin_hlsl_wave_active_bit_xor:
|
|
case Builtin::BI__builtin_hlsl_wave_active_bit_and: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
// Ensure input expr type is a scalar/vector
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
|
|
if (CheckWaveActive(&SemaRef, TheCall))
|
|
return true;
|
|
|
|
// Ensure the expr type is interpretable as a uint or vector<uint>
|
|
ExprResult Expr = TheCall->getArg(0);
|
|
QualType ArgTyExpr = Expr.get()->getType();
|
|
auto *VTy = ArgTyExpr->getAs<VectorType>();
|
|
if (!(ArgTyExpr->isIntegerType() ||
|
|
(VTy && VTy->getElementType()->isIntegerType()))) {
|
|
SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_builtin_invalid_arg_type)
|
|
<< ArgTyExpr << SemaRef.Context.UnsignedIntTy << 1 << 0 << 0;
|
|
return true;
|
|
}
|
|
|
|
// Ensure input expr type is the same as the return type
|
|
TheCall->setType(ArgTyExpr);
|
|
break;
|
|
}
|
|
// Note these are llvm builtins that we want to catch invalid intrinsic
|
|
// generation. Normal handling of these builtins will occur elsewhere.
|
|
case Builtin::BI__builtin_elementwise_bitreverse: {
|
|
// does not include a check for number of arguments
|
|
// because that is done previously
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckUnsignedIntRepresentation))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_wave_prefix_count_bits: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
QualType ArgType = TheCall->getArg(0)->getType();
|
|
|
|
if (!(ArgType->isScalarType())) {
|
|
SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_typecheck_expect_any_scalar_or_vector)
|
|
<< ArgType << 0;
|
|
return true;
|
|
}
|
|
|
|
if (!(ArgType->isBooleanType())) {
|
|
SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_typecheck_expect_any_scalar_or_vector)
|
|
<< ArgType << 0;
|
|
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_wave_prefix_sum:
|
|
case Builtin::BI__builtin_hlsl_wave_prefix_product: {
|
|
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 (CheckWavePrefix(&SemaRef, TheCall))
|
|
return true;
|
|
ExprResult Expr = TheCall->getArg(0);
|
|
QualType ArgTyExpr = Expr.get()->getType();
|
|
TheCall->setType(ArgTyExpr);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_quad_read_across_x: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
|
|
if (CheckAnyScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
if (CheckNotBoolScalarOrVector(&SemaRef, TheCall, 0))
|
|
return true;
|
|
ExprResult Expr = TheCall->getArg(0);
|
|
QualType ArgTyExpr = Expr.get()->getType();
|
|
TheCall->setType(ArgTyExpr);
|
|
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 (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckFloatOrHalfRepresentation))
|
|
return true;
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_buffer_update_counter: {
|
|
assert(TheCall->getNumArgs() == 2 && "expected 2 args");
|
|
auto checkResTy = [](const HLSLAttributedResourceType *ResTy) -> bool {
|
|
return !(ResTy->getAttrs().ResourceClass == ResourceClass::UAV &&
|
|
ResTy->getAttrs().RawBuffer && ResTy->hasContainedType());
|
|
};
|
|
if (CheckResourceHandle(&SemaRef, TheCall, 0, checkResTy))
|
|
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;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_f16tof32: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall,
|
|
CheckUnsignedIntRepresentation))
|
|
return true;
|
|
// ensure arg integers are 32 bits
|
|
if (CheckExpectedBitWidth(&SemaRef, TheCall, 0, 32))
|
|
return true;
|
|
// check it wasn't a bool type
|
|
QualType ArgTy = TheCall->getArg(0)->getType();
|
|
if (auto *VTy = ArgTy->getAs<VectorType>())
|
|
ArgTy = VTy->getElementType();
|
|
if (ArgTy->isBooleanType()) {
|
|
SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
|
|
diag::err_builtin_invalid_arg_type)
|
|
<< 1 << /* scalar or vector of */ 5 << /* unsigned int */ 3
|
|
<< /* no fp */ 0 << TheCall->getArg(0)->getType();
|
|
return true;
|
|
}
|
|
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall, getASTContext().FloatTy);
|
|
break;
|
|
}
|
|
case Builtin::BI__builtin_hlsl_elementwise_f32tof16: {
|
|
if (SemaRef.checkArgCount(TheCall, 1))
|
|
return true;
|
|
if (CheckAllArgTypesAreCorrect(&SemaRef, TheCall, CheckFloatRepresentation))
|
|
return true;
|
|
SetElementTypeAsReturnType(&SemaRef, TheCall,
|
|
getASTContext().UnsignedIntTy);
|
|
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();
|
|
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)
|
|
llvm::append_range(List, ElementFields);
|
|
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 *MT = dyn_cast<ConstantMatrixType>(T)) {
|
|
List.insert(List.end(), MT->getNumElementsFlattened(),
|
|
MT->getElementType());
|
|
continue;
|
|
}
|
|
if (const auto *RD = T->getAsCXXRecordDecl()) {
|
|
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())
|
|
if (!FD->isUnnamedBitField())
|
|
FieldTypes.push_back(FD->getType());
|
|
// Reverse the newly added sub-range.
|
|
std::reverse(FieldTypes.begin(), FieldTypes.end());
|
|
llvm::append_range(WorkList, FieldTypes);
|
|
|
|
// 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());
|
|
llvm::append_range(WorkList, FieldTypes);
|
|
}
|
|
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");
|
|
}
|
|
|
|
// Can perform an HLSL Aggregate splat cast if the Dest is an aggregate and the
|
|
// Src is a scalar, a vector of length 1, or a 1x1 matrix
|
|
// Or if Dest is a vector and Src is a vector of length 1 or a 1x1 matrix
|
|
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>();
|
|
const ConstantMatrixType *SrcMatTy = SrcTy->getAs<ConstantMatrixType>();
|
|
|
|
// Src isn't a scalar, a vector of length 1, or a 1x1 matrix
|
|
if (!SrcTy->isScalarType() &&
|
|
!(SrcVecTy && SrcVecTy->getNumElements() == 1) &&
|
|
!(SrcMatTy && SrcMatTy->getNumElementsFlattened() == 1))
|
|
return false;
|
|
|
|
if (SrcVecTy)
|
|
SrcTy = SrcVecTy->getElementType();
|
|
else if (SrcMatTy)
|
|
SrcTy = SrcMatTy->getElementType();
|
|
|
|
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?
|
|
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 (SrcTy->isConstantMatrixType() &&
|
|
(DestTy->isScalarType() || DestTy->isConstantMatrixType()))
|
|
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;
|
|
}
|
|
|
|
// Returns true if the type has a non-empty constant buffer layout (if it is
|
|
// scalar, vector or matrix, or if it contains any of these.
|
|
static bool hasConstantBufferLayout(QualType QT) {
|
|
const Type *Ty = QT->getUnqualifiedDesugaredType();
|
|
if (Ty->isScalarType() || Ty->isVectorType() || Ty->isMatrixType())
|
|
return true;
|
|
|
|
if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray())
|
|
return false;
|
|
|
|
if (const auto *RD = Ty->getAsCXXRecordDecl()) {
|
|
for (const auto *FD : RD->fields()) {
|
|
if (hasConstantBufferLayout(FD->getType()))
|
|
return true;
|
|
}
|
|
assert(RD->getNumBases() <= 1 &&
|
|
"HLSL doesn't support multiple inheritance");
|
|
return RD->getNumBases()
|
|
? hasConstantBufferLayout(RD->bases_begin()->getType())
|
|
: false;
|
|
}
|
|
|
|
if (const auto *AT = dyn_cast<ArrayType>(Ty)) {
|
|
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
|
|
if (isZeroSizedArray(CAT))
|
|
return false;
|
|
return hasConstantBufferLayout(AT->getElementType());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool IsDefaultBufferConstantDecl(const ASTContext &Ctx, VarDecl *VD) {
|
|
bool IsVulkan =
|
|
Ctx.getTargetInfo().getTriple().getOS() == llvm::Triple::Vulkan;
|
|
bool IsVKPushConstant = IsVulkan && VD->hasAttr<HLSLVkPushConstantAttr>();
|
|
QualType QT = VD->getType();
|
|
return VD->getDeclContext()->isTranslationUnit() &&
|
|
QT.getAddressSpace() == LangAS::Default &&
|
|
VD->getStorageClass() != SC_Static &&
|
|
!VD->hasAttr<HLSLVkConstantIdAttr>() && !IsVKPushConstant &&
|
|
hasConstantBufferLayout(QT);
|
|
}
|
|
|
|
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 (Decl->hasAttr<HLSLVkExtBuiltinInputAttr>()) {
|
|
LangAS ImplAS = LangAS::hlsl_input;
|
|
Type = SemaRef.getASTContext().getAddrSpaceQualType(Type, ImplAS);
|
|
Decl->setType(Type);
|
|
return;
|
|
}
|
|
|
|
bool IsVulkan = getASTContext().getTargetInfo().getTriple().getOS() ==
|
|
llvm::Triple::Vulkan;
|
|
if (IsVulkan && Decl->hasAttr<HLSLVkPushConstantAttr>()) {
|
|
if (HasDeclaredAPushConstant)
|
|
SemaRef.Diag(Decl->getLocation(), diag::err_hlsl_push_constant_unique);
|
|
|
|
LangAS ImplAS = LangAS::hlsl_push_constant;
|
|
Type = SemaRef.getASTContext().getAddrSpaceQualType(Type, ImplAS);
|
|
Decl->setType(Type);
|
|
HasDeclaredAPushConstant = true;
|
|
return;
|
|
}
|
|
|
|
if (Type->isSamplerT() || Type->isVoidType())
|
|
return;
|
|
|
|
// Resource handles.
|
|
if (Type->isHLSLResourceRecord() || Type->isHLSLResourceRecordArray())
|
|
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);
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Helper class for assigning bindings to resources declared within a struct.
|
|
// It keeps track of all binding attributes declared on a struct instance, and
|
|
// the offsets for each register type that have been assigned so far.
|
|
// Handles both explicit and implicit bindings.
|
|
class StructBindingContext {
|
|
// Bindings and offsets per register type. We only need to support four
|
|
// register types - SRV (u), UAV (t), CBuffer (c), and Sampler (s).
|
|
HLSLResourceBindingAttr *RegBindingsAttrs[4];
|
|
unsigned RegBindingOffset[4];
|
|
|
|
// Make sure the RegisterType values are what we expect
|
|
static_assert(static_cast<unsigned>(RegisterType::SRV) == 0 &&
|
|
static_cast<unsigned>(RegisterType::UAV) == 1 &&
|
|
static_cast<unsigned>(RegisterType::CBuffer) == 2 &&
|
|
static_cast<unsigned>(RegisterType::Sampler) == 3,
|
|
"unexpected register type values");
|
|
|
|
// Vulkan binding attribute does not vary by register type.
|
|
HLSLVkBindingAttr *VkBindingAttr;
|
|
unsigned VkBindingOffset;
|
|
|
|
public:
|
|
// Constructor: gather all binding attributes on a struct instance and
|
|
// initialize offsets.
|
|
StructBindingContext(VarDecl *VD) {
|
|
for (unsigned i = 0; i < 4; ++i) {
|
|
RegBindingsAttrs[i] = nullptr;
|
|
RegBindingOffset[i] = 0;
|
|
}
|
|
VkBindingAttr = nullptr;
|
|
VkBindingOffset = 0;
|
|
|
|
ASTContext &AST = VD->getASTContext();
|
|
bool IsSpirv = AST.getTargetInfo().getTriple().isSPIRV();
|
|
|
|
for (Attr *A : VD->attrs()) {
|
|
if (auto *RBA = dyn_cast<HLSLResourceBindingAttr>(A)) {
|
|
RegisterType RegType = RBA->getRegisterType();
|
|
unsigned RegTypeIdx = static_cast<unsigned>(RegType);
|
|
// Ignore unsupported register annotations, such as 'c' or 'i'.
|
|
if (RegTypeIdx < 4)
|
|
RegBindingsAttrs[RegTypeIdx] = RBA;
|
|
continue;
|
|
}
|
|
// Gather the Vulkan binding attributes only if the target is SPIR-V.
|
|
if (IsSpirv) {
|
|
if (auto *VBA = dyn_cast<HLSLVkBindingAttr>(A))
|
|
VkBindingAttr = VBA;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Creates a binding attribute for a resource based on the gathered attributes
|
|
// and the required register type and range.
|
|
Attr *createBindingAttr(SemaHLSL &S, ASTContext &AST, RegisterType RegType,
|
|
unsigned Range) {
|
|
assert(static_cast<unsigned>(RegType) < 4 && "unexpected register type");
|
|
|
|
if (VkBindingAttr) {
|
|
unsigned Offset = VkBindingOffset;
|
|
VkBindingOffset += Range;
|
|
return HLSLVkBindingAttr::CreateImplicit(
|
|
AST, VkBindingAttr->getBinding() + Offset, VkBindingAttr->getSet(),
|
|
VkBindingAttr->getRange());
|
|
}
|
|
|
|
HLSLResourceBindingAttr *RBA =
|
|
RegBindingsAttrs[static_cast<unsigned>(RegType)];
|
|
HLSLResourceBindingAttr *NewAttr = nullptr;
|
|
|
|
if (RBA && RBA->hasRegisterSlot()) {
|
|
// Explicit binding - create a new attribute with offseted slot number
|
|
// based on the required register type.
|
|
unsigned Offset = RegBindingOffset[static_cast<unsigned>(RegType)];
|
|
RegBindingOffset[static_cast<unsigned>(RegType)] += Range;
|
|
|
|
unsigned NewSlotNumber = RBA->getSlotNumber() + Offset;
|
|
StringRef NewSlotNumberStr =
|
|
createRegisterString(AST, RBA->getRegisterType(), NewSlotNumber);
|
|
NewAttr = HLSLResourceBindingAttr::CreateImplicit(
|
|
AST, NewSlotNumberStr, RBA->getSpace(), RBA->getRange());
|
|
NewAttr->setBinding(RegType, NewSlotNumber, RBA->getSpaceNumber());
|
|
} else {
|
|
// No binding attribute or space-only binding - create a binding
|
|
// attribute for implicit binding.
|
|
NewAttr = HLSLResourceBindingAttr::CreateImplicit(AST, "", "0", {});
|
|
NewAttr->setBinding(RegType, std::nullopt,
|
|
RBA ? RBA->getSpaceNumber() : 0);
|
|
NewAttr->setImplicitBindingOrderID(S.getNextImplicitBindingOrderID());
|
|
}
|
|
return NewAttr;
|
|
}
|
|
};
|
|
|
|
// Creates a global variable declaration for a resource field embedded in a
|
|
// struct, assigns it a binding, initializes it, and associates it with the
|
|
// struct declaration via an HLSLAssociatedResourceDeclAttr.
|
|
static void createGlobalResourceDeclForStruct(
|
|
Sema &S, VarDecl *ParentVD, SourceLocation Loc, IdentifierInfo *Id,
|
|
QualType ResTy, StructBindingContext &BindingCtx) {
|
|
assert(isResourceRecordTypeOrArrayOf(ResTy) &&
|
|
"expected resource type or array of resources");
|
|
|
|
DeclContext *DC = ParentVD->getNonTransparentDeclContext();
|
|
assert(DC->isTranslationUnit() && "expected translation unit decl context");
|
|
|
|
ASTContext &AST = S.getASTContext();
|
|
VarDecl *ResDecl =
|
|
VarDecl::Create(AST, DC, Loc, Loc, Id, ResTy, nullptr, SC_None);
|
|
|
|
unsigned Range = 1;
|
|
const HLSLAttributedResourceType *ResHandleTy = nullptr;
|
|
if (const auto *AT = dyn_cast<ArrayType>(ResTy.getTypePtr())) {
|
|
const auto *CAT = dyn_cast<ConstantArrayType>(AT);
|
|
Range = CAT ? CAT->getSize().getZExtValue() : 0;
|
|
ResHandleTy = getResourceArrayHandleType(ResTy);
|
|
} else {
|
|
ResHandleTy = HLSLAttributedResourceType::findHandleTypeOnResource(
|
|
ResTy.getTypePtr());
|
|
}
|
|
// Add a binding attribute to the global resource declaration.
|
|
Attr *BindingAttr = BindingCtx.createBindingAttr(
|
|
S.HLSL(), AST, getRegisterType(ResHandleTy), Range);
|
|
ResDecl->addAttr(BindingAttr);
|
|
ResDecl->addAttr(InternalLinkageAttr::CreateImplicit(AST));
|
|
ResDecl->setImplicit();
|
|
|
|
if (Range == 1)
|
|
S.HLSL().initGlobalResourceDecl(ResDecl);
|
|
else
|
|
S.HLSL().initGlobalResourceArrayDecl(ResDecl);
|
|
|
|
ParentVD->addAttr(
|
|
HLSLAssociatedResourceDeclAttr::CreateImplicit(AST, ResDecl));
|
|
DC->addDecl(ResDecl);
|
|
|
|
DeclGroupRef DG(ResDecl);
|
|
S.Consumer.HandleTopLevelDecl(DG);
|
|
}
|
|
|
|
static void handleArrayOfStructWithResources(
|
|
Sema &S, VarDecl *ParentVD, const ConstantArrayType *CAT,
|
|
EmbeddedResourceNameBuilder &NameBuilder, StructBindingContext &BindingCtx);
|
|
|
|
// Scans base and all fields of a struct/class type to find all embedded
|
|
// resources or resource arrays. Creates a global variable for each resource
|
|
// found.
|
|
static void handleStructWithResources(Sema &S, VarDecl *ParentVD,
|
|
const CXXRecordDecl *RD,
|
|
EmbeddedResourceNameBuilder &NameBuilder,
|
|
StructBindingContext &BindingCtx) {
|
|
|
|
// Scan the base classes.
|
|
assert(RD->getNumBases() <= 1 && "HLSL doesn't support multiple inheritance");
|
|
const auto *BasesIt = RD->bases_begin();
|
|
if (BasesIt != RD->bases_end()) {
|
|
QualType QT = BasesIt->getType();
|
|
if (QT->isHLSLIntangibleType()) {
|
|
CXXRecordDecl *BaseRD = QT->getAsCXXRecordDecl();
|
|
NameBuilder.pushBaseName(BaseRD->getName());
|
|
handleStructWithResources(S, ParentVD, BaseRD, NameBuilder, BindingCtx);
|
|
NameBuilder.pop();
|
|
}
|
|
}
|
|
// Process this class fields.
|
|
for (const FieldDecl *FD : RD->fields()) {
|
|
QualType FDTy = FD->getType().getCanonicalType();
|
|
if (!FDTy->isHLSLIntangibleType())
|
|
continue;
|
|
|
|
NameBuilder.pushName(FD->getName());
|
|
|
|
if (isResourceRecordTypeOrArrayOf(FDTy)) {
|
|
IdentifierInfo *II = NameBuilder.getNameAsIdentifier(S.getASTContext());
|
|
createGlobalResourceDeclForStruct(S, ParentVD, FD->getLocation(), II,
|
|
FDTy, BindingCtx);
|
|
} else if (const auto *RD = FDTy->getAsCXXRecordDecl()) {
|
|
handleStructWithResources(S, ParentVD, RD, NameBuilder, BindingCtx);
|
|
|
|
} else if (const auto *ArrayTy = dyn_cast<ConstantArrayType>(FDTy)) {
|
|
assert(!FDTy->isHLSLResourceRecordArray() &&
|
|
"resource arrays should have been already handled");
|
|
handleArrayOfStructWithResources(S, ParentVD, ArrayTy, NameBuilder,
|
|
BindingCtx);
|
|
}
|
|
NameBuilder.pop();
|
|
}
|
|
}
|
|
|
|
// Processes array of structs with resources.
|
|
static void
|
|
handleArrayOfStructWithResources(Sema &S, VarDecl *ParentVD,
|
|
const ConstantArrayType *CAT,
|
|
EmbeddedResourceNameBuilder &NameBuilder,
|
|
StructBindingContext &BindingCtx) {
|
|
|
|
QualType ElementTy = CAT->getElementType().getCanonicalType();
|
|
assert(ElementTy->isHLSLIntangibleType() && "Expected HLSL intangible type");
|
|
|
|
const ConstantArrayType *SubCAT = dyn_cast<ConstantArrayType>(ElementTy);
|
|
const CXXRecordDecl *ElementRD = ElementTy->getAsCXXRecordDecl();
|
|
|
|
if (!SubCAT && !ElementRD)
|
|
return;
|
|
|
|
for (unsigned I = 0, E = CAT->getSize().getZExtValue(); I < E; ++I) {
|
|
NameBuilder.pushArrayIndex(I);
|
|
if (ElementRD)
|
|
handleStructWithResources(S, ParentVD, ElementRD, NameBuilder,
|
|
BindingCtx);
|
|
else
|
|
handleArrayOfStructWithResources(S, ParentVD, SubCAT, NameBuilder,
|
|
BindingCtx);
|
|
NameBuilder.pop();
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Scans all fields of a user-defined struct (or array of structs)
|
|
// to find all embedded resources or resource arrays. For each resource
|
|
// a global variable of the resource type is created and associated
|
|
// with the parent declaration (VD) through a HLSLAssociatedResourceDeclAttr
|
|
// attribute.
|
|
void SemaHLSL::handleGlobalStructOrArrayOfWithResources(VarDecl *VD) {
|
|
EmbeddedResourceNameBuilder NameBuilder(VD->getName());
|
|
StructBindingContext BindingCtx(VD);
|
|
|
|
const Type *VDTy = VD->getType().getTypePtr();
|
|
assert(VDTy->isHLSLIntangibleType() && !isResourceRecordTypeOrArrayOf(VD) &&
|
|
"Expected non-resource struct or array type");
|
|
|
|
if (const CXXRecordDecl *RD = VDTy->getAsCXXRecordDecl()) {
|
|
handleStructWithResources(SemaRef, VD, RD, NameBuilder, BindingCtx);
|
|
return;
|
|
}
|
|
|
|
if (const auto *CAT = dyn_cast<ConstantArrayType>(VDTy)) {
|
|
handleArrayOfStructWithResources(SemaRef, VD, CAT, NameBuilder, BindingCtx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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(getASTContext(), 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);
|
|
|
|
if (VD->hasAttr<HLSLVkConstantIdAttr>())
|
|
VD->setStorageClass(StorageClass::SC_Static);
|
|
|
|
if (isResourceRecordTypeOrArrayOf(VD) &&
|
|
VD->getStorageClass() != SC_Static) {
|
|
// Add internal linkage attribute to non-static resource variables. The
|
|
// global externally visible storage is accessed through the handle, which
|
|
// is a member. The variable itself is not externally visible.
|
|
VD->addAttr(InternalLinkageAttr::CreateImplicit(getASTContext()));
|
|
}
|
|
|
|
// process explicit bindings
|
|
processExplicitBindingsOnDecl(VD);
|
|
|
|
// Add implicit binding attribute to non-static resource arrays.
|
|
if (VD->getType()->isHLSLResourceRecordArray() &&
|
|
VD->getStorageClass() != SC_Static) {
|
|
// If the resource array does not have an explicit binding attribute,
|
|
// create an implicit one. It will be used to transfer implicit binding
|
|
// order_ID to codegen.
|
|
ResourceBindingAttrs Binding(VD);
|
|
if (!Binding.isExplicit()) {
|
|
uint32_t OrderID = getNextImplicitBindingOrderID();
|
|
if (Binding.hasBinding())
|
|
Binding.setImplicitOrderID(OrderID);
|
|
else {
|
|
addImplicitBindingAttrToDecl(
|
|
SemaRef, VD, getRegisterType(getResourceArrayHandleType(VD)),
|
|
OrderID);
|
|
// Re-create the binding object to pick up the new attribute.
|
|
Binding = ResourceBindingAttrs(VD);
|
|
}
|
|
}
|
|
|
|
// Get to the base type of a potentially multi-dimensional array.
|
|
QualType Ty = getASTContext().getBaseElementType(VD->getType());
|
|
|
|
const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
|
|
if (hasCounterHandle(RD)) {
|
|
if (!Binding.hasCounterImplicitOrderID()) {
|
|
uint32_t OrderID = getNextImplicitBindingOrderID();
|
|
Binding.setCounterImplicitOrderID(OrderID);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process resources in user-defined structs, or arrays of such structs.
|
|
const Type *VDTy = VD->getType().getTypePtr();
|
|
if (VD->getStorageClass() != SC_Static && VDTy->isHLSLIntangibleType() &&
|
|
!isResourceRecordTypeOrArrayOf(VD))
|
|
handleGlobalStructOrArrayOfWithResources(VD);
|
|
|
|
// Mark groupshared variables as extern so they will have
|
|
// external storage and won't be default initialized
|
|
if (VD->hasAttr<HLSLGroupSharedAddressSpaceAttr>())
|
|
VD->setStorageClass(StorageClass::SC_Extern);
|
|
}
|
|
|
|
deduceAddressSpace(VD);
|
|
}
|
|
|
|
bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
|
|
assert(VD->getType()->isHLSLResourceRecord() &&
|
|
"expected resource record type");
|
|
|
|
ASTContext &AST = SemaRef.getASTContext();
|
|
uint64_t UIntTySize = AST.getTypeSize(AST.UnsignedIntTy);
|
|
uint64_t IntTySize = AST.getTypeSize(AST.IntTy);
|
|
|
|
// Gather resource binding attributes.
|
|
ResourceBindingAttrs Binding(VD);
|
|
|
|
// Find correct initialization method and create its arguments.
|
|
QualType ResourceTy = VD->getType();
|
|
CXXRecordDecl *ResourceDecl = ResourceTy->getAsCXXRecordDecl();
|
|
CXXMethodDecl *CreateMethod = nullptr;
|
|
llvm::SmallVector<Expr *> Args;
|
|
|
|
bool HasCounter = hasCounterHandle(ResourceDecl);
|
|
const char *CreateMethodName;
|
|
if (Binding.isExplicit())
|
|
CreateMethodName = HasCounter ? "__createFromBindingWithImplicitCounter"
|
|
: "__createFromBinding";
|
|
else
|
|
CreateMethodName = HasCounter
|
|
? "__createFromImplicitBindingWithImplicitCounter"
|
|
: "__createFromImplicitBinding";
|
|
|
|
CreateMethod =
|
|
lookupMethod(SemaRef, ResourceDecl, CreateMethodName, VD->getLocation());
|
|
|
|
if (!CreateMethod)
|
|
// This can happen if someone creates a struct that looks like an HLSL
|
|
// resource record but does not have the required static create method.
|
|
// No binding will be generated for it.
|
|
return false;
|
|
|
|
if (Binding.isExplicit()) {
|
|
IntegerLiteral *RegSlot =
|
|
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, Binding.getSlot()),
|
|
AST.UnsignedIntTy, SourceLocation());
|
|
Args.push_back(RegSlot);
|
|
} else {
|
|
uint32_t OrderID = (Binding.hasImplicitOrderID())
|
|
? Binding.getImplicitOrderID()
|
|
: getNextImplicitBindingOrderID();
|
|
IntegerLiteral *OrderId =
|
|
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, OrderID),
|
|
AST.UnsignedIntTy, SourceLocation());
|
|
Args.push_back(OrderId);
|
|
}
|
|
|
|
IntegerLiteral *Space =
|
|
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, Binding.getSpace()),
|
|
AST.UnsignedIntTy, SourceLocation());
|
|
Args.push_back(Space);
|
|
|
|
IntegerLiteral *RangeSize = IntegerLiteral::Create(
|
|
AST, llvm::APInt(IntTySize, 1), AST.IntTy, SourceLocation());
|
|
Args.push_back(RangeSize);
|
|
|
|
IntegerLiteral *Index = IntegerLiteral::Create(
|
|
AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy, SourceLocation());
|
|
Args.push_back(Index);
|
|
|
|
StringRef VarName = VD->getName();
|
|
StringLiteral *Name = StringLiteral::Create(
|
|
AST, VarName, StringLiteralKind::Ordinary, false,
|
|
AST.getStringLiteralArrayType(AST.CharTy.withConst(), VarName.size()),
|
|
SourceLocation());
|
|
ImplicitCastExpr *NameCast = ImplicitCastExpr::Create(
|
|
AST, AST.getPointerType(AST.CharTy.withConst()), CK_ArrayToPointerDecay,
|
|
Name, nullptr, VK_PRValue, FPOptionsOverride());
|
|
Args.push_back(NameCast);
|
|
|
|
if (HasCounter) {
|
|
// Will this be in the correct order?
|
|
uint32_t CounterOrderID = getNextImplicitBindingOrderID();
|
|
IntegerLiteral *CounterId =
|
|
IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, CounterOrderID),
|
|
AST.UnsignedIntTy, SourceLocation());
|
|
Args.push_back(CounterId);
|
|
}
|
|
|
|
// Make sure the create method template is instantiated and emitted.
|
|
if (!CreateMethod->isDefined() && CreateMethod->isTemplateInstantiation())
|
|
SemaRef.InstantiateFunctionDefinition(VD->getLocation(), CreateMethod,
|
|
true);
|
|
|
|
// Create CallExpr with a call to the static method and set it as the decl
|
|
// initialization.
|
|
DeclRefExpr *DRE = DeclRefExpr::Create(
|
|
AST, NestedNameSpecifierLoc(), SourceLocation(), CreateMethod, false,
|
|
CreateMethod->getNameInfo(), CreateMethod->getType(), VK_PRValue);
|
|
|
|
auto *ImpCast = ImplicitCastExpr::Create(
|
|
AST, AST.getPointerType(CreateMethod->getType()),
|
|
CK_FunctionToPointerDecay, DRE, nullptr, VK_PRValue, FPOptionsOverride());
|
|
|
|
CallExpr *InitExpr =
|
|
CallExpr::Create(AST, ImpCast, Args, ResourceTy, VK_PRValue,
|
|
SourceLocation(), FPOptionsOverride());
|
|
VD->setInit(InitExpr);
|
|
VD->setInitStyle(VarDecl::CallInit);
|
|
SemaRef.CheckCompleteVariableDeclaration(VD);
|
|
return true;
|
|
}
|
|
|
|
bool SemaHLSL::initGlobalResourceArrayDecl(VarDecl *VD) {
|
|
assert(VD->getType()->isHLSLResourceRecordArray() &&
|
|
"expected array of resource records");
|
|
|
|
// Individual resources in a resource array are not initialized here. They
|
|
// are initialized later on during codegen when the individual resources are
|
|
// accessed. Codegen will emit a call to the resource initialization method
|
|
// with the specified array index. We need to make sure though that the method
|
|
// for the specific resource type is instantiated, so codegen can emit a call
|
|
// to it when the array element is accessed.
|
|
|
|
// Find correct initialization method based on the resource binding
|
|
// information.
|
|
ASTContext &AST = SemaRef.getASTContext();
|
|
QualType ResElementTy = AST.getBaseElementType(VD->getType());
|
|
CXXRecordDecl *ResourceDecl = ResElementTy->getAsCXXRecordDecl();
|
|
CXXMethodDecl *CreateMethod = nullptr;
|
|
|
|
bool HasCounter = hasCounterHandle(ResourceDecl);
|
|
ResourceBindingAttrs ResourceAttrs(VD);
|
|
if (ResourceAttrs.isExplicit())
|
|
// Resource has explicit binding.
|
|
CreateMethod =
|
|
lookupMethod(SemaRef, ResourceDecl,
|
|
HasCounter ? "__createFromBindingWithImplicitCounter"
|
|
: "__createFromBinding",
|
|
VD->getLocation());
|
|
else
|
|
// Resource has implicit binding.
|
|
CreateMethod = lookupMethod(
|
|
SemaRef, ResourceDecl,
|
|
HasCounter ? "__createFromImplicitBindingWithImplicitCounter"
|
|
: "__createFromImplicitBinding",
|
|
VD->getLocation());
|
|
|
|
if (!CreateMethod)
|
|
return false;
|
|
|
|
// Make sure the create method template is instantiated and emitted.
|
|
if (!CreateMethod->isDefined() && CreateMethod->isTemplateInstantiation())
|
|
SemaRef.InstantiateFunctionDefinition(VD->getLocation(), CreateMethod,
|
|
true);
|
|
return true;
|
|
}
|
|
|
|
// Returns true if the initialization has been handled.
|
|
// Returns false to use default initialization.
|
|
bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) {
|
|
// Objects in the hlsl_constant address space are initialized
|
|
// externally, so don't synthesize an implicit initializer.
|
|
if (VD->getType().getAddressSpace() == LangAS::hlsl_constant)
|
|
return true;
|
|
|
|
// Initialize non-static resources at the global scope.
|
|
if (VD->hasGlobalStorage() && VD->getStorageClass() != SC_Static) {
|
|
const Type *Ty = VD->getType().getTypePtr();
|
|
if (Ty->isHLSLResourceRecord())
|
|
return initGlobalResourceDecl(VD);
|
|
if (Ty->isHLSLResourceRecordArray())
|
|
return initGlobalResourceArrayDecl(VD);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::optional<const DeclBindingInfo *> SemaHLSL::inferGlobalBinding(Expr *E) {
|
|
if (auto *Ternary = dyn_cast<ConditionalOperator>(E)) {
|
|
auto TrueInfo = inferGlobalBinding(Ternary->getTrueExpr());
|
|
auto FalseInfo = inferGlobalBinding(Ternary->getFalseExpr());
|
|
if (!TrueInfo || !FalseInfo)
|
|
return std::nullopt;
|
|
if (*TrueInfo != *FalseInfo)
|
|
return std::nullopt;
|
|
return TrueInfo;
|
|
}
|
|
|
|
if (auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
|
|
E = ASE->getBase()->IgnoreParenImpCasts();
|
|
|
|
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens()))
|
|
if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
|
|
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
|
|
if (Ty->isArrayType())
|
|
Ty = Ty->getArrayElementTypeNoTypeQual();
|
|
|
|
if (const auto *AttrResType =
|
|
HLSLAttributedResourceType::findHandleTypeOnResource(Ty)) {
|
|
ResourceClass RC = AttrResType->getAttrs().ResourceClass;
|
|
return Bindings.getDeclBindingInfo(VD, RC);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SemaHLSL::trackLocalResource(VarDecl *VD, Expr *E) {
|
|
std::optional<const DeclBindingInfo *> ExprBinding = inferGlobalBinding(E);
|
|
if (!ExprBinding) {
|
|
SemaRef.Diag(E->getBeginLoc(),
|
|
diag::warn_hlsl_assigning_local_resource_is_not_unique)
|
|
<< E << VD;
|
|
return; // Expr use multiple resources
|
|
}
|
|
|
|
if (*ExprBinding == nullptr)
|
|
return; // No binding could be inferred to track, return without error
|
|
|
|
auto PrevBinding = Assigns.find(VD);
|
|
if (PrevBinding == Assigns.end()) {
|
|
// No previous binding recorded, simply record the new assignment
|
|
Assigns.insert({VD, *ExprBinding});
|
|
return;
|
|
}
|
|
|
|
// Otherwise, warn if the assignment implies different resource bindings
|
|
if (*ExprBinding != PrevBinding->second) {
|
|
SemaRef.Diag(E->getBeginLoc(),
|
|
diag::warn_hlsl_assigning_local_resource_is_not_unique)
|
|
<< E << VD;
|
|
SemaRef.Diag(VD->getLocation(), diag::note_var_declared_here) << VD;
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool SemaHLSL::CheckResourceBinOp(BinaryOperatorKind Opc, Expr *LHSExpr,
|
|
Expr *RHSExpr, SourceLocation Loc) {
|
|
assert((LHSExpr->getType()->isHLSLResourceRecord() ||
|
|
LHSExpr->getType()->isHLSLResourceRecordArray()) &&
|
|
"expected LHS to be a resource record or array of resource records");
|
|
if (Opc != BO_Assign)
|
|
return true;
|
|
|
|
// If LHS is an array subscript, get the underlying declaration.
|
|
Expr *E = LHSExpr;
|
|
while (auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
|
|
E = ASE->getBase()->IgnoreParenImpCasts();
|
|
|
|
// Report error if LHS is a non-static resource declared at a global scope.
|
|
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens())) {
|
|
if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
|
|
if (VD->hasGlobalStorage() && VD->getStorageClass() != SC_Static) {
|
|
// assignment to global resource is not allowed
|
|
SemaRef.Diag(Loc, diag::err_hlsl_assign_to_global_resource) << VD;
|
|
SemaRef.Diag(VD->getLocation(), diag::note_var_declared_here) << VD;
|
|
return false;
|
|
}
|
|
|
|
trackLocalResource(VD, RHSExpr);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 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->isArrayType()) {
|
|
const ArrayType *AT = cast<ArrayType>(Ty);
|
|
Ty = AT->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");
|
|
|
|
bool HasBinding = false;
|
|
for (Attr *A : VD->attrs()) {
|
|
if (isa<HLSLVkBindingAttr>(A)) {
|
|
HasBinding = true;
|
|
if (auto PA = VD->getAttr<HLSLVkPushConstantAttr>())
|
|
Diag(PA->getLoc(), diag::err_hlsl_attr_incompatible) << A << PA;
|
|
}
|
|
|
|
HLSLResourceBindingAttr *RBA = dyn_cast<HLSLResourceBindingAttr>(A);
|
|
if (!RBA || !RBA->hasRegisterSlot())
|
|
continue;
|
|
HasBinding = true;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (!HasBinding && isResourceRecordTypeOrArrayOf(VD))
|
|
SemaRef.Diag(VD->getLocation(), diag::warn_hlsl_implicit_binding);
|
|
}
|
|
namespace {
|
|
class InitListTransformer {
|
|
Sema &S;
|
|
ASTContext &Ctx;
|
|
QualType InitTy;
|
|
QualType *DstIt = nullptr;
|
|
Expr **ArgIt = nullptr;
|
|
// Is wrapping the destination type iterator required? This is only used for
|
|
// incomplete array types where we loop over the destination type since we
|
|
// don't know the full number of elements from the declaration.
|
|
bool Wrap;
|
|
|
|
bool castInitializer(Expr *E) {
|
|
assert(DstIt && "This should always be something!");
|
|
if (DstIt == DestTypes.end()) {
|
|
if (!Wrap) {
|
|
ArgExprs.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;
|
|
}
|
|
DstIt = DestTypes.begin();
|
|
}
|
|
InitializedEntity Entity = InitializedEntity::InitializeParameter(
|
|
Ctx, *DstIt, /* Consumed (ObjC) */ false);
|
|
ExprResult Res = S.PerformCopyInitialization(Entity, E->getBeginLoc(), E);
|
|
if (Res.isInvalid())
|
|
return false;
|
|
Expr *Init = Res.get();
|
|
ArgExprs.push_back(Init);
|
|
DstIt++;
|
|
return true;
|
|
}
|
|
|
|
bool buildInitializerListImpl(Expr *E) {
|
|
// If this is an initialization list, traverse the sub initializers.
|
|
if (auto *Init = dyn_cast<InitListExpr>(E)) {
|
|
for (auto *SubInit : Init->inits())
|
|
if (!buildInitializerListImpl(SubInit))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// If this is a scalar type, just enqueue the expression.
|
|
QualType Ty = E->getType().getDesugaredType(Ctx);
|
|
|
|
if (Ty->isScalarType() || (Ty->isRecordType() && !Ty->isAggregateType()) ||
|
|
Ty->isHLSLAttributedResourceType())
|
|
return castInitializer(E);
|
|
|
|
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(ElExpr.get()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (auto *MTy = Ty->getAs<ConstantMatrixType>()) {
|
|
unsigned Rows = MTy->getNumRows();
|
|
unsigned Cols = MTy->getNumColumns();
|
|
QualType ElemTy = MTy->getElementType();
|
|
|
|
for (unsigned R = 0; R < Rows; ++R) {
|
|
for (unsigned C = 0; C < Cols; ++C) {
|
|
// row index literal
|
|
Expr *RowIdx = IntegerLiteral::Create(
|
|
Ctx, llvm::APInt(Ctx.getIntWidth(Ctx.IntTy), R), Ctx.IntTy,
|
|
E->getBeginLoc());
|
|
// column index literal
|
|
Expr *ColIdx = IntegerLiteral::Create(
|
|
Ctx, llvm::APInt(Ctx.getIntWidth(Ctx.IntTy), C), Ctx.IntTy,
|
|
E->getBeginLoc());
|
|
ExprResult ElExpr = S.CreateBuiltinMatrixSubscriptExpr(
|
|
E, RowIdx, ColIdx, E->getEndLoc());
|
|
if (ElExpr.isInvalid())
|
|
return false;
|
|
if (!castInitializer(ElExpr.get()))
|
|
return false;
|
|
ElExpr.get()->setType(ElemTy);
|
|
}
|
|
}
|
|
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 (!buildInitializerListImpl(ElExpr.get()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (auto *RD = Ty->getAsCXXRecordDecl()) {
|
|
llvm::SmallVector<CXXRecordDecl *> RecordDecls;
|
|
RecordDecls.push_back(RD);
|
|
while (RecordDecls.back()->getNumBases()) {
|
|
CXXRecordDecl *D = RecordDecls.back();
|
|
assert(D->getNumBases() == 1 &&
|
|
"HLSL doesn't support multiple inheritance");
|
|
RecordDecls.push_back(
|
|
D->bases_begin()->getType()->castAsCXXRecordDecl());
|
|
}
|
|
while (!RecordDecls.empty()) {
|
|
CXXRecordDecl *RD = RecordDecls.pop_back_val();
|
|
for (auto *FD : RD->fields()) {
|
|
if (FD->isUnnamedBitField())
|
|
continue;
|
|
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 (!buildInitializerListImpl(Res.get()))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Expr *generateInitListsImpl(QualType Ty) {
|
|
Ty = Ty.getDesugaredType(Ctx);
|
|
assert(ArgIt != ArgExprs.end() && "Something is off in iteration!");
|
|
if (Ty->isScalarType() || (Ty->isRecordType() && !Ty->isAggregateType()) ||
|
|
Ty->isHLSLAttributedResourceType())
|
|
return *(ArgIt++);
|
|
|
|
llvm::SmallVector<Expr *> Inits;
|
|
if (Ty->isVectorType() || Ty->isConstantArrayType() ||
|
|
Ty->isConstantMatrixType()) {
|
|
QualType ElTy;
|
|
uint64_t Size = 0;
|
|
if (auto *ATy = Ty->getAs<VectorType>()) {
|
|
ElTy = ATy->getElementType();
|
|
Size = ATy->getNumElements();
|
|
} else if (auto *CMTy = Ty->getAs<ConstantMatrixType>()) {
|
|
ElTy = CMTy->getElementType();
|
|
Size = CMTy->getNumElementsFlattened();
|
|
} else {
|
|
auto *VTy = cast<ConstantArrayType>(Ty.getTypePtr());
|
|
ElTy = VTy->getElementType();
|
|
Size = VTy->getZExtSize();
|
|
}
|
|
for (uint64_t I = 0; I < Size; ++I)
|
|
Inits.push_back(generateInitListsImpl(ElTy));
|
|
}
|
|
if (auto *RD = Ty->getAsCXXRecordDecl()) {
|
|
llvm::SmallVector<CXXRecordDecl *> RecordDecls;
|
|
RecordDecls.push_back(RD);
|
|
while (RecordDecls.back()->getNumBases()) {
|
|
CXXRecordDecl *D = RecordDecls.back();
|
|
assert(D->getNumBases() == 1 &&
|
|
"HLSL doesn't support multiple inheritance");
|
|
RecordDecls.push_back(
|
|
D->bases_begin()->getType()->castAsCXXRecordDecl());
|
|
}
|
|
while (!RecordDecls.empty()) {
|
|
CXXRecordDecl *RD = RecordDecls.pop_back_val();
|
|
for (auto *FD : RD->fields())
|
|
if (!FD->isUnnamedBitField())
|
|
Inits.push_back(generateInitListsImpl(FD->getType()));
|
|
}
|
|
}
|
|
auto *NewInit = new (Ctx) InitListExpr(Ctx, Inits.front()->getBeginLoc(),
|
|
Inits, Inits.back()->getEndLoc());
|
|
NewInit->setType(Ty);
|
|
return NewInit;
|
|
}
|
|
|
|
public:
|
|
llvm::SmallVector<QualType, 16> DestTypes;
|
|
llvm::SmallVector<Expr *, 16> ArgExprs;
|
|
InitListTransformer(Sema &SemaRef, const InitializedEntity &Entity)
|
|
: S(SemaRef), Ctx(SemaRef.getASTContext()),
|
|
Wrap(Entity.getType()->isIncompleteArrayType()) {
|
|
InitTy = Entity.getType().getNonReferenceType();
|
|
// When we're generating initializer lists for incomplete array types we
|
|
// need to wrap around both when building the initializers and when
|
|
// generating the final initializer lists.
|
|
if (Wrap) {
|
|
assert(InitTy->isIncompleteArrayType());
|
|
const IncompleteArrayType *IAT = Ctx.getAsIncompleteArrayType(InitTy);
|
|
InitTy = IAT->getElementType();
|
|
}
|
|
BuildFlattenedTypeList(InitTy, DestTypes);
|
|
DstIt = DestTypes.begin();
|
|
}
|
|
|
|
bool buildInitializerList(Expr *E) { return buildInitializerListImpl(E); }
|
|
|
|
Expr *generateInitLists() {
|
|
assert(!ArgExprs.empty() &&
|
|
"Call buildInitializerList to generate argument expressions.");
|
|
ArgIt = ArgExprs.begin();
|
|
if (!Wrap)
|
|
return generateInitListsImpl(InitTy);
|
|
llvm::SmallVector<Expr *> Inits;
|
|
while (ArgIt != ArgExprs.end())
|
|
Inits.push_back(generateInitListsImpl(InitTy));
|
|
|
|
auto *NewInit = new (Ctx) InitListExpr(Ctx, Inits.front()->getBeginLoc(),
|
|
Inits, Inits.back()->getEndLoc());
|
|
llvm::APInt ArySize(64, Inits.size());
|
|
NewInit->setType(Ctx.getConstantArrayType(InitTy, ArySize, nullptr,
|
|
ArraySizeModifier::Normal, 0));
|
|
return NewInit;
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
// Recursively detect any incomplete array anywhere in the type graph,
|
|
// including arrays, struct fields, and base classes.
|
|
static bool containsIncompleteArrayType(QualType Ty) {
|
|
Ty = Ty.getCanonicalType();
|
|
|
|
// Array types
|
|
if (const ArrayType *AT = dyn_cast<ArrayType>(Ty)) {
|
|
if (isa<IncompleteArrayType>(AT))
|
|
return true;
|
|
return containsIncompleteArrayType(AT->getElementType());
|
|
}
|
|
|
|
// Record (struct/class) types
|
|
if (const auto *RT = Ty->getAs<RecordType>()) {
|
|
const RecordDecl *RD = RT->getDecl();
|
|
|
|
// Walk base classes (for C++ / HLSL structs with inheritance)
|
|
if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
|
|
for (const CXXBaseSpecifier &Base : CXXRD->bases()) {
|
|
if (containsIncompleteArrayType(Base.getType()))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Walk fields
|
|
for (const FieldDecl *F : RD->fields()) {
|
|
if (containsIncompleteArrayType(F->getType()))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SemaHLSL::transformInitList(const InitializedEntity &Entity,
|
|
InitListExpr *Init) {
|
|
// If the initializer is a scalar, just return it.
|
|
if (Init->getType()->isScalarType())
|
|
return true;
|
|
ASTContext &Ctx = SemaRef.getASTContext();
|
|
InitListTransformer ILT(SemaRef, Entity);
|
|
|
|
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 (!ILT.buildInitializerList(E))
|
|
return false;
|
|
}
|
|
size_t ExpectedSize = ILT.DestTypes.size();
|
|
size_t ActualSize = ILT.ArgExprs.size();
|
|
if (ExpectedSize == 0 && ActualSize == 0)
|
|
return true;
|
|
|
|
// Reject empty initializer if *any* incomplete array exists structurally
|
|
if (ActualSize == 0 && containsIncompleteArrayType(Entity.getType())) {
|
|
QualType InitTy = Entity.getType().getNonReferenceType();
|
|
if (InitTy.hasAddressSpace())
|
|
InitTy = SemaRef.getASTContext().removeAddrSpaceQualType(InitTy);
|
|
|
|
SemaRef.Diag(Init->getBeginLoc(), diag::err_hlsl_incorrect_num_initializers)
|
|
<< /*TooManyOrFew=*/(int)(ExpectedSize < ActualSize) << InitTy
|
|
<< /*ExpectedSize=*/ExpectedSize << /*ActualSize=*/ActualSize;
|
|
return false;
|
|
}
|
|
|
|
// We infer size after validating legality.
|
|
// For incomplete arrays it is completely arbitrary to choose whether we think
|
|
// the user intended fewer or more elements. This implementation assumes that
|
|
// the user intended more, and errors that there are too few initializers to
|
|
// complete the final element.
|
|
if (Entity.getType()->isIncompleteArrayType()) {
|
|
assert(ExpectedSize > 0 &&
|
|
"The expected size of an incomplete array type must be at least 1.");
|
|
ExpectedSize =
|
|
((ActualSize + ExpectedSize - 1) / ExpectedSize) * ExpectedSize;
|
|
}
|
|
|
|
// 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();
|
|
if (InitTy.hasAddressSpace())
|
|
InitTy = SemaRef.getASTContext().removeAddrSpaceQualType(InitTy);
|
|
if (ExpectedSize != ActualSize) {
|
|
int TooManyOrFew = ActualSize > ExpectedSize ? 1 : 0;
|
|
SemaRef.Diag(Init->getBeginLoc(), diag::err_hlsl_incorrect_num_initializers)
|
|
<< TooManyOrFew << InitTy << ExpectedSize << ActualSize;
|
|
return false;
|
|
}
|
|
|
|
// generateInitListsImpl will always return an InitListExpr here, because the
|
|
// scalar case is handled above.
|
|
auto *NewInit = cast<InitListExpr>(ILT.generateInitLists());
|
|
Init->resizeInits(Ctx, NewInit->getNumInits());
|
|
for (unsigned I = 0; I < NewInit->getNumInits(); ++I)
|
|
Init->updateInit(Ctx, I, NewInit->getInit(I));
|
|
return true;
|
|
}
|
|
|
|
static QualType ReportMatrixInvalidMember(Sema &S, StringRef Name,
|
|
StringRef Expected,
|
|
SourceLocation OpLoc,
|
|
SourceLocation CompLoc) {
|
|
S.Diag(OpLoc, diag::err_builtin_matrix_invalid_member)
|
|
<< Name << Expected << SourceRange(CompLoc);
|
|
return QualType();
|
|
}
|
|
|
|
QualType SemaHLSL::checkMatrixComponent(Sema &S, QualType baseType,
|
|
ExprValueKind &VK, SourceLocation OpLoc,
|
|
const IdentifierInfo *CompName,
|
|
SourceLocation CompLoc) {
|
|
const auto *MT = baseType->castAs<ConstantMatrixType>();
|
|
StringRef AccessorName = CompName->getName();
|
|
assert(!AccessorName.empty() && "Matrix Accessor must have a name");
|
|
|
|
unsigned Rows = MT->getNumRows();
|
|
unsigned Cols = MT->getNumColumns();
|
|
bool IsZeroBasedAccessor = false;
|
|
unsigned ChunkLen = 0;
|
|
if (AccessorName.size() < 2)
|
|
return ReportMatrixInvalidMember(S, AccessorName,
|
|
"length 4 for zero based: \'_mRC\' or "
|
|
"length 3 for one-based: \'_RC\' accessor",
|
|
OpLoc, CompLoc);
|
|
|
|
if (AccessorName[0] == '_') {
|
|
if (AccessorName[1] == 'm') {
|
|
IsZeroBasedAccessor = true;
|
|
ChunkLen = 4; // zero-based: "_mRC"
|
|
} else {
|
|
ChunkLen = 3; // one-based: "_RC"
|
|
}
|
|
} else
|
|
return ReportMatrixInvalidMember(
|
|
S, AccessorName, "zero based: \'_mRC\' or one-based: \'_RC\' accessor",
|
|
OpLoc, CompLoc);
|
|
|
|
if (AccessorName.size() % ChunkLen != 0) {
|
|
const llvm::StringRef Expected = IsZeroBasedAccessor
|
|
? "zero based: '_mRC' accessor"
|
|
: "one-based: '_RC' accessor";
|
|
|
|
return ReportMatrixInvalidMember(S, AccessorName, Expected, OpLoc, CompLoc);
|
|
}
|
|
|
|
auto isDigit = [](char c) { return c >= '0' && c <= '9'; };
|
|
auto isZeroBasedIndex = [](unsigned i) { return i <= 3; };
|
|
auto isOneBasedIndex = [](unsigned i) { return i >= 1 && i <= 4; };
|
|
|
|
bool HasRepeated = false;
|
|
SmallVector<bool, 16> Seen(Rows * Cols, false);
|
|
unsigned NumComponents = 0;
|
|
const char *Begin = AccessorName.data();
|
|
|
|
for (unsigned I = 0, E = AccessorName.size(); I < E; I += ChunkLen) {
|
|
const char *Chunk = Begin + I;
|
|
char RowChar = 0, ColChar = 0;
|
|
if (IsZeroBasedAccessor) {
|
|
// Zero-based: "_mRC"
|
|
if (Chunk[0] != '_' || Chunk[1] != 'm') {
|
|
char Bad = (Chunk[0] != '_') ? Chunk[0] : Chunk[1];
|
|
return ReportMatrixInvalidMember(
|
|
S, StringRef(&Bad, 1), "\'_m\' prefix",
|
|
OpLoc.getLocWithOffset(I + (Bad == Chunk[0] ? 1 : 2)), CompLoc);
|
|
}
|
|
RowChar = Chunk[2];
|
|
ColChar = Chunk[3];
|
|
} else {
|
|
// One-based: "_RC"
|
|
if (Chunk[0] != '_')
|
|
return ReportMatrixInvalidMember(
|
|
S, StringRef(&Chunk[0], 1), "\'_\' prefix",
|
|
OpLoc.getLocWithOffset(I + 1), CompLoc);
|
|
RowChar = Chunk[1];
|
|
ColChar = Chunk[2];
|
|
}
|
|
|
|
// Must be digits.
|
|
bool IsDigitsError = false;
|
|
if (!isDigit(RowChar)) {
|
|
unsigned BadPos = IsZeroBasedAccessor ? 2 : 1;
|
|
ReportMatrixInvalidMember(S, StringRef(&RowChar, 1), "row as integer",
|
|
OpLoc.getLocWithOffset(I + BadPos + 1),
|
|
CompLoc);
|
|
IsDigitsError = true;
|
|
}
|
|
|
|
if (!isDigit(ColChar)) {
|
|
unsigned BadPos = IsZeroBasedAccessor ? 3 : 2;
|
|
ReportMatrixInvalidMember(S, StringRef(&ColChar, 1), "column as integer",
|
|
OpLoc.getLocWithOffset(I + BadPos + 1),
|
|
CompLoc);
|
|
IsDigitsError = true;
|
|
}
|
|
if (IsDigitsError)
|
|
return QualType();
|
|
|
|
unsigned Row = RowChar - '0';
|
|
unsigned Col = ColChar - '0';
|
|
|
|
bool HasIndexingError = false;
|
|
if (IsZeroBasedAccessor) {
|
|
// 0-based [0..3]
|
|
if (!isZeroBasedIndex(Row)) {
|
|
S.Diag(OpLoc, diag::err_hlsl_matrix_element_not_in_bounds)
|
|
<< /*row*/ 0 << /*zero-based*/ 0 << SourceRange(CompLoc);
|
|
HasIndexingError = true;
|
|
}
|
|
if (!isZeroBasedIndex(Col)) {
|
|
S.Diag(OpLoc, diag::err_hlsl_matrix_element_not_in_bounds)
|
|
<< /*col*/ 1 << /*zero-based*/ 0 << SourceRange(CompLoc);
|
|
HasIndexingError = true;
|
|
}
|
|
} else {
|
|
// 1-based [1..4]
|
|
if (!isOneBasedIndex(Row)) {
|
|
S.Diag(OpLoc, diag::err_hlsl_matrix_element_not_in_bounds)
|
|
<< /*row*/ 0 << /*one-based*/ 1 << SourceRange(CompLoc);
|
|
HasIndexingError = true;
|
|
}
|
|
if (!isOneBasedIndex(Col)) {
|
|
S.Diag(OpLoc, diag::err_hlsl_matrix_element_not_in_bounds)
|
|
<< /*col*/ 1 << /*one-based*/ 1 << SourceRange(CompLoc);
|
|
HasIndexingError = true;
|
|
}
|
|
// Convert to 0-based after range checking.
|
|
--Row;
|
|
--Col;
|
|
}
|
|
|
|
if (HasIndexingError)
|
|
return QualType();
|
|
|
|
// Note: matrix swizzle index is hard coded. That means Row and Col can
|
|
// potentially be larger than Rows and Cols if matrix size is less than
|
|
// the max index size.
|
|
bool HasBoundsError = false;
|
|
if (Row >= Rows) {
|
|
Diag(OpLoc, diag::err_hlsl_matrix_index_out_of_bounds)
|
|
<< /*Row*/ 0 << Row << Rows << SourceRange(CompLoc);
|
|
HasBoundsError = true;
|
|
}
|
|
if (Col >= Cols) {
|
|
Diag(OpLoc, diag::err_hlsl_matrix_index_out_of_bounds)
|
|
<< /*Col*/ 1 << Col << Cols << SourceRange(CompLoc);
|
|
HasBoundsError = true;
|
|
}
|
|
if (HasBoundsError)
|
|
return QualType();
|
|
|
|
unsigned FlatIndex = Row * Cols + Col;
|
|
if (Seen[FlatIndex])
|
|
HasRepeated = true;
|
|
Seen[FlatIndex] = true;
|
|
++NumComponents;
|
|
}
|
|
if (NumComponents == 0 || NumComponents > 4) {
|
|
S.Diag(OpLoc, diag::err_hlsl_matrix_swizzle_invalid_length)
|
|
<< NumComponents << SourceRange(CompLoc);
|
|
return QualType();
|
|
}
|
|
|
|
QualType ElemTy = MT->getElementType();
|
|
if (NumComponents == 1)
|
|
return ElemTy;
|
|
QualType VT = S.Context.getExtVectorType(ElemTy, NumComponents);
|
|
if (HasRepeated)
|
|
VK = VK_PRValue;
|
|
|
|
for (Sema::ExtVectorDeclsType::iterator
|
|
I = S.ExtVectorDecls.begin(S.getExternalSource()),
|
|
E = S.ExtVectorDecls.end();
|
|
I != E; ++I) {
|
|
if ((*I)->getUnderlyingType() == VT)
|
|
return S.Context.getTypedefType(ElaboratedTypeKeyword::None,
|
|
/*Qualifier=*/std::nullopt, *I);
|
|
}
|
|
|
|
return VT;
|
|
}
|
|
|
|
bool SemaHLSL::handleInitialization(VarDecl *VDecl, Expr *&Init) {
|
|
// If initializing a local resource, track the resource binding it is using
|
|
if (VDecl->getType()->isHLSLResourceRecord() && !VDecl->hasGlobalStorage())
|
|
trackLocalResource(VDecl, Init);
|
|
|
|
const HLSLVkConstantIdAttr *ConstIdAttr =
|
|
VDecl->getAttr<HLSLVkConstantIdAttr>();
|
|
if (!ConstIdAttr)
|
|
return true;
|
|
|
|
ASTContext &Context = SemaRef.getASTContext();
|
|
|
|
APValue InitValue;
|
|
if (!Init->isCXX11ConstantExpr(Context, &InitValue)) {
|
|
Diag(VDecl->getLocation(), diag::err_specialization_const);
|
|
VDecl->setInvalidDecl();
|
|
return false;
|
|
}
|
|
|
|
Builtin::ID BID =
|
|
getSpecConstBuiltinId(VDecl->getType()->getUnqualifiedDesugaredType());
|
|
|
|
// Argument 1: The ID from the attribute
|
|
int ConstantID = ConstIdAttr->getId();
|
|
llvm::APInt IDVal(Context.getIntWidth(Context.IntTy), ConstantID);
|
|
Expr *IdExpr = IntegerLiteral::Create(Context, IDVal, Context.IntTy,
|
|
ConstIdAttr->getLocation());
|
|
|
|
SmallVector<Expr *, 2> Args = {IdExpr, Init};
|
|
Expr *C = SemaRef.BuildBuiltinCallExpr(Init->getExprLoc(), BID, Args);
|
|
if (C->getType()->getCanonicalTypeUnqualified() !=
|
|
VDecl->getType()->getCanonicalTypeUnqualified()) {
|
|
C = SemaRef
|
|
.BuildCStyleCastExpr(SourceLocation(),
|
|
Context.getTrivialTypeSourceInfo(
|
|
Init->getType(), Init->getExprLoc()),
|
|
SourceLocation(), C)
|
|
.get();
|
|
}
|
|
Init = C;
|
|
return true;
|
|
}
|
|
|
|
QualType SemaHLSL::ActOnTemplateShorthand(TemplateDecl *Template,
|
|
SourceLocation NameLoc) {
|
|
if (!Template)
|
|
return QualType();
|
|
|
|
DeclContext *DC = Template->getDeclContext();
|
|
if (!DC->isNamespace() || !cast<NamespaceDecl>(DC)->getIdentifier() ||
|
|
cast<NamespaceDecl>(DC)->getName() != "hlsl")
|
|
return QualType();
|
|
|
|
TemplateParameterList *Params = Template->getTemplateParameters();
|
|
if (!Params || Params->size() != 1)
|
|
return QualType();
|
|
|
|
if (!Template->isImplicit())
|
|
return QualType();
|
|
|
|
// We manually extract default arguments here instead of letting
|
|
// CheckTemplateIdType handle it. This ensures that for resource types that
|
|
// lack a default argument (like Buffer), we return a null QualType, which
|
|
// triggers the "requires template arguments" error rather than a less
|
|
// descriptive "too few template arguments" error.
|
|
TemplateArgumentListInfo TemplateArgs(NameLoc, NameLoc);
|
|
for (NamedDecl *P : *Params) {
|
|
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) {
|
|
if (TTP->hasDefaultArgument()) {
|
|
TemplateArgs.addArgument(TTP->getDefaultArgument());
|
|
continue;
|
|
}
|
|
} else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P)) {
|
|
if (NTTP->hasDefaultArgument()) {
|
|
TemplateArgs.addArgument(NTTP->getDefaultArgument());
|
|
continue;
|
|
}
|
|
} else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P)) {
|
|
if (TTPD->hasDefaultArgument()) {
|
|
TemplateArgs.addArgument(TTPD->getDefaultArgument());
|
|
continue;
|
|
}
|
|
}
|
|
return QualType();
|
|
}
|
|
|
|
return SemaRef.CheckTemplateIdType(
|
|
ElaboratedTypeKeyword::None, TemplateName(Template), NameLoc,
|
|
TemplateArgs, nullptr, /*ForNestedNameSpecifier=*/false);
|
|
}
|