This attribute is similar to the already implemented ext_builtin_input attribute. One important bit is the `static` storage class: HLSL uses static differently than C/C++. This is a known weirdness: See https://github.com/microsoft/hlsl-specs/issues/350 In C/C++, when we declare a variable as 'extern', we often expect another module to declare the symbole. In HLSL, the pipeline will 'declare' the symbol. Hence in this case, we need to emit the global variable. Related WG-HLSL: https://github.com/llvm/wg-hlsl/blob/main/proposals/0031-semantics.md --------- Co-authored-by: Steven Perron <stevenperron@google.com>
6197 lines
224 KiB
C++
6197 lines
224 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::handleVkExtBuiltinOutputAttr(Decl *D, const ParsedAttr &AL) {
|
|
uint32_t ID;
|
|
if (!SemaRef.checkUInt32Argument(AL, AL.getArgAsExpr(0), ID))
|
|
return;
|
|
D->addAttr(::new (getASTContext())
|
|
HLSLVkExtBuiltinOutputAttr(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;
|
|
}
|
|
|
|
if (Decl->hasAttr<HLSLVkExtBuiltinOutputAttr>()) {
|
|
LangAS ImplAS = LangAS::hlsl_output;
|
|
Type = SemaRef.getASTContext().getAddrSpaceQualType(Type, ImplAS);
|
|
Decl->setType(Type);
|
|
|
|
// HLSL uses `static` differently than C++. For BuiltIn output, the static
|
|
// does not imply private to the module scope.
|
|
// Marking it as external to reflect the semantic this attribute brings.
|
|
// See https://github.com/microsoft/hlsl-specs/issues/350
|
|
Decl->setStorageClass(SC_Extern);
|
|
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);
|
|
}
|