Skip to content

Commit d31eebe

Browse files
committed
[HLSL] Implement HLSL intialization list support
This PR implements HLSL's initialization list behvaior as specified in the draft language specifcation under [*Decl.Init.Agg*](https://microsoft.github.io/hlsl-specs/specs/hlsl.html #Decl.Init.Agg). This behavior is a bit unusual for C/C++ because intermediate braces in initializer lists are ignored and a whole array of additional conversions occur unintuitively to how initializaiton works in C. The implementaiton in this PR generates a valid C/C++ initialization list AST for the HLSL initializer so that there are no changes required to Clang's CodeGen to support this. This design will also allow us to use Clang's rewrite to convert HLSL initializers to valid C/C++ initializers that are equivalent. It does have the downside that it will generate often redundant accesses during codegen. The IR optimizer is extremely good at eliminating those so this will have no impact on the final executable performance. There is some opportunity for optimizing the initializer list generation that we could consider in subsequent commits. One notable opportunity would be to identify aggregate objects that occur in the same place in both initializers and do not require converison, those aggregates could be initialized as aggregates rather than fully scalarized. Closes llvm#56067
1 parent b403004 commit d31eebe

File tree

9 files changed

+964
-5
lines changed

9 files changed

+964
-5
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12626,6 +12626,9 @@ def err_hlsl_pointers_unsupported : Error<
1262612626
"%select{pointers|references}0 are unsupported in HLSL">;
1262712627
def err_hlsl_missing_resource_class : Error<"HLSL resource needs to have [[hlsl::resource_class()]] attribute">;
1262812628
def err_hlsl_attribute_needs_intangible_type: Error<"attribute %0 can be used only on HLSL intangible type %1">;
12629+
def err_hlsl_incorrect_num_initializers: Error<
12630+
"too %select{few|many}0 initializers in list for type %1 "
12631+
"(expected %2 but found %3)">;
1262912632

1263012633
def err_hlsl_operator_unsupported : Error<
1263112634
"the '%select{&|*|->}0' operator is unsupported in HLSL">;

clang/include/clang/Sema/SemaHLSL.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
namespace clang {
2727
class AttributeCommonInfo;
2828
class IdentifierInfo;
29+
class InitializedEntity;
30+
class InitializationKind;
2931
class ParsedAttr;
3032
class Scope;
3133
class VarDecl;
@@ -149,6 +151,9 @@ class SemaHLSL : public SemaBase {
149151

150152
QualType getInoutParameterType(QualType Ty);
151153

154+
bool TransformInitList(const InitializedEntity &Entity,
155+
const InitializationKind &Kind, InitListExpr *Init);
156+
152157
private:
153158
// HLSL resource type attributes need to be processed all at once.
154159
// This is a list to collect them.

clang/lib/Sema/SemaChecking.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11678,9 +11678,12 @@ static void AnalyzeImplicitConversions(
1167811678

1167911679
// Propagate whether we are in a C++ list initialization expression.
1168011680
// If so, we do not issue warnings for implicit int-float conversion
11681-
// precision loss, because C++11 narrowing already handles it.
11682-
bool IsListInit = Item.IsListInit ||
11683-
(isa<InitListExpr>(OrigE) && S.getLangOpts().CPlusPlus);
11681+
// precision loss, because C++11 narrowing already handles it. HLSL's
11682+
// initialization lists are special, so they shouldn't observe the C++
11683+
// behavior here.
11684+
bool IsListInit =
11685+
Item.IsListInit || (isa<InitListExpr>(OrigE) &&
11686+
S.getLangOpts().CPlusPlus && !S.getLangOpts().HLSL);
1168411687

1168511688
if (E->isTypeDependent() || E->isValueDependent())
1168611689
return;

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,3 +3056,162 @@ void SemaHLSL::processExplicitBindingsOnDecl(VarDecl *VD) {
30563056
}
30573057
}
30583058
}
3059+
3060+
static bool CastInitializer(Sema &S, ASTContext &Ctx, Expr *E,
3061+
llvm::SmallVectorImpl<Expr *> &List,
3062+
llvm::SmallVectorImpl<QualType> &DestTypes) {
3063+
if (List.size() >= DestTypes.size())
3064+
return false;
3065+
InitializedEntity Entity =
3066+
InitializedEntity::InitializeParameter(Ctx, DestTypes[List.size()], false);
3067+
ExprResult Res =
3068+
S.PerformCopyInitialization(Entity, E->getBeginLoc(), E);
3069+
if (Res.isInvalid())
3070+
return false;
3071+
Expr *Init = Res.get();
3072+
List.push_back(Init);
3073+
return true;
3074+
}
3075+
3076+
static void BuildIntializerList(Sema &S, ASTContext &Ctx, Expr *E,
3077+
llvm::SmallVectorImpl<Expr *> &List,
3078+
llvm::SmallVectorImpl<QualType> &DestTypes,
3079+
bool &ExcessInits) {
3080+
if (List.size() >= DestTypes.size()) {
3081+
ExcessInits = true;
3082+
return;
3083+
}
3084+
3085+
// If this is an initialization list, traverse the sub initializers.
3086+
if (auto *Init = dyn_cast<InitListExpr>(E)) {
3087+
for (auto *SubInit : Init->inits())
3088+
BuildIntializerList(S, Ctx, SubInit, List, DestTypes, ExcessInits);
3089+
return;
3090+
}
3091+
3092+
// If this is a scalar type, just enqueue the expression.
3093+
QualType Ty = E->getType();
3094+
if (Ty->isScalarType()) {
3095+
(void)CastInitializer(S, Ctx, E, List, DestTypes);
3096+
return;
3097+
}
3098+
3099+
if (auto *ATy = Ty->getAs<VectorType>()) {
3100+
uint64_t Size = ATy->getNumElements();
3101+
3102+
if (List.size() + Size > DestTypes.size()) {
3103+
ExcessInits = true;
3104+
return;
3105+
}
3106+
QualType SizeTy = Ctx.getSizeType();
3107+
uint64_t SizeTySize = Ctx.getTypeSize(SizeTy);
3108+
for (uint64_t I = 0; I < Size; ++I) {
3109+
auto *Idx = IntegerLiteral::Create(Ctx, llvm::APInt(SizeTySize, I),
3110+
SizeTy, SourceLocation());
3111+
3112+
ExprResult ElExpr = S.CreateBuiltinArraySubscriptExpr(
3113+
E, E->getBeginLoc(), Idx, E->getEndLoc());
3114+
if (ElExpr.isInvalid())
3115+
return;
3116+
if (!CastInitializer(S, Ctx, ElExpr.get(), List, DestTypes))
3117+
return;
3118+
}
3119+
return;
3120+
}
3121+
3122+
if (auto *VTy = dyn_cast<ConstantArrayType>(Ty.getTypePtr())) {
3123+
uint64_t Size = VTy->getZExtSize();
3124+
QualType SizeTy = Ctx.getSizeType();
3125+
uint64_t SizeTySize = Ctx.getTypeSize(SizeTy);
3126+
for (uint64_t I = 0; I < Size; ++I) {
3127+
auto *Idx = IntegerLiteral::Create(Ctx, llvm::APInt(SizeTySize, I),
3128+
SizeTy, SourceLocation());
3129+
ExprResult ElExpr = S.CreateBuiltinArraySubscriptExpr(
3130+
E, E->getBeginLoc(), Idx, E->getEndLoc());
3131+
if (ElExpr.isInvalid())
3132+
return;
3133+
BuildIntializerList(S, Ctx, ElExpr.get(), List, DestTypes, ExcessInits);
3134+
}
3135+
return;
3136+
}
3137+
3138+
if (auto *RTy = Ty->getAs<RecordType>()) {
3139+
for (auto *FD : RTy->getDecl()->fields()) {
3140+
DeclAccessPair Found = DeclAccessPair::make(FD, FD->getAccess());
3141+
DeclarationNameInfo NameInfo(FD->getDeclName(), E->getBeginLoc());
3142+
ExprResult Res = S.BuildFieldReferenceExpr(
3143+
E, false, E->getBeginLoc(), CXXScopeSpec(), FD, Found, NameInfo);
3144+
if (Res.isInvalid())
3145+
return;
3146+
BuildIntializerList(S, Ctx, Res.get(), List, DestTypes, ExcessInits);
3147+
}
3148+
}
3149+
}
3150+
3151+
static Expr *GenerateInitLists(ASTContext &Ctx, QualType Ty,
3152+
llvm::SmallVectorImpl<Expr *>::iterator &It) {
3153+
if (Ty->isScalarType()) {
3154+
return *(It++);
3155+
}
3156+
llvm::SmallVector<Expr *> Inits;
3157+
assert(!isa<MatrixType>(Ty) && "Matrix types not yet supported in HLSL");
3158+
if (Ty->isVectorType() || Ty->isConstantArrayType()) {
3159+
QualType ElTy;
3160+
uint64_t Size = 0;
3161+
if (auto *ATy = Ty->getAs<VectorType>()) {
3162+
ElTy = ATy->getElementType();
3163+
Size = ATy->getNumElements();
3164+
} else {
3165+
auto *VTy = cast<ConstantArrayType>(Ty.getTypePtr());
3166+
ElTy = VTy->getElementType();
3167+
Size = VTy->getZExtSize();
3168+
}
3169+
for (uint64_t I = 0; I < Size; ++I)
3170+
Inits.push_back(GenerateInitLists(Ctx, ElTy, It));
3171+
}
3172+
if (const RecordDecl *RD = Ty->getAsRecordDecl()) {
3173+
for (auto *FD : RD->fields()) {
3174+
Inits.push_back(GenerateInitLists(Ctx, FD->getType(), It));
3175+
}
3176+
}
3177+
auto *NewInit = new (Ctx) InitListExpr(Ctx, Inits.front()->getBeginLoc(),
3178+
Inits, Inits.back()->getEndLoc());
3179+
NewInit->setType(Ty);
3180+
return NewInit;
3181+
}
3182+
3183+
bool SemaHLSL::TransformInitList(const InitializedEntity &Entity,
3184+
const InitializationKind &Kind,
3185+
InitListExpr *Init) {
3186+
// If the initializer is a scalar, just return it.
3187+
if (Init->getType()->isScalarType())
3188+
return true;
3189+
ASTContext &Ctx = SemaRef.getASTContext();
3190+
llvm::SmallVector<QualType, 16> DestTypes;
3191+
// An initializer list might be attempting to initialize a reference or
3192+
// rvalue-reference. When checking the initializer we should look through the
3193+
// reference.
3194+
QualType InitTy = Entity.getType().getNonReferenceType();
3195+
BuildFlattenedTypeList(InitTy, DestTypes);
3196+
3197+
llvm::SmallVector<Expr *, 16> ArgExprs;
3198+
bool ExcessInits = false;
3199+
for (Expr *Arg : Init->inits())
3200+
BuildIntializerList(SemaRef, Ctx, Arg, ArgExprs, DestTypes, ExcessInits);
3201+
3202+
if (DestTypes.size() != ArgExprs.size() || ExcessInits) {
3203+
int TooManyOrFew = ExcessInits ? 1 : 0;
3204+
SemaRef.Diag(Init->getBeginLoc(), diag::err_hlsl_incorrect_num_initializers)
3205+
<< TooManyOrFew << InitTy << DestTypes.size() << ArgExprs.size();
3206+
return false;
3207+
}
3208+
3209+
auto It = ArgExprs.begin();
3210+
// GenerateInitLists will always return an InitListExpr here, because the
3211+
// scalar case is handled above.
3212+
auto *NewInit = cast<InitListExpr>(GenerateInitLists(Ctx, InitTy, It));
3213+
Init->resizeInits(Ctx, NewInit->getNumInits());
3214+
for (unsigned I = 0; I < NewInit->getNumInits(); ++I)
3215+
Init->updateInit(Ctx, I, NewInit->getInit(I));
3216+
return true;
3217+
}

clang/lib/Sema/SemaInit.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "clang/Sema/Initialization.h"
2727
#include "clang/Sema/Lookup.h"
2828
#include "clang/Sema/Ownership.h"
29+
#include "clang/Sema/SemaHLSL.h"
2930
#include "clang/Sema/SemaObjC.h"
3031
#include "llvm/ADT/APInt.h"
3132
#include "llvm/ADT/FoldingSet.h"
@@ -4787,6 +4788,10 @@ static void TryListInitialization(Sema &S,
47874788
bool TreatUnavailableAsInvalid) {
47884789
QualType DestType = Entity.getType();
47894790

4791+
if (S.getLangOpts().HLSL &&
4792+
!S.HLSL().TransformInitList(Entity, Kind, InitList))
4793+
return;
4794+
47904795
// C++ doesn't allow scalar initialization with more than one argument.
47914796
// But C99 complex numbers are scalars and it makes sense there.
47924797
if (S.getLangOpts().CPlusPlus && DestType->isScalarType() &&

clang/test/CodeGenHLSL/ArrayTemporary.hlsl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
23

34
void fn(float x[2]) { }
@@ -27,7 +28,7 @@ void fn2(Obj O[4]) { }
2728
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp]], ptr align 4 [[Arr]], i32 32, i1 false)
2829
// CHECK: call void {{.*}}fn2{{.*}}(ptr noundef byval([4 x %struct.Obj]) align 4 [[Tmp]])
2930
void call2() {
30-
Obj Arr[4] = {};
31+
Obj Arr[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
3132
fn2(Arr);
3233
}
3334

0 commit comments

Comments
 (0)