Skip to content

Commit 4bab038

Browse files
[HLSL] Add __builtin_hlsl_is_scalarized_layout_compatible (#102227)
HLSL tends to rely pretty aggressively on scalarization occuring in the complier, which allows for some relaxed language behaviors when types are fully sclarized to equivalent scalar representations. This change adds a new queryable trait builtin for scalarized layout compatability. Resolves #100614 --------- Co-authored-by: Aaron Ballman <[email protected]>
1 parent ec4d5a6 commit 4bab038

File tree

6 files changed

+302
-0
lines changed

6 files changed

+302
-0
lines changed

clang/include/clang/Basic/TokenKinds.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,9 @@ KEYWORD(out , KEYHLSL)
660660
#define HLSL_INTANGIBLE_TYPE(Name, Id, SingletonId) KEYWORD(Name, KEYHLSL)
661661
#include "clang/Basic/HLSLIntangibleTypes.def"
662662

663+
// HLSL Type traits.
664+
TYPE_TRAIT_2(__builtin_hlsl_is_scalarized_layout_compatible, IsScalarizedLayoutCompatible, KEYHLSL)
665+
663666
// OpenMP Type Traits
664667
UNARY_EXPR_OR_TYPE_TRAIT(__builtin_omp_required_simd_align, OpenMPRequiredSimdAlign, KEYALL)
665668

clang/include/clang/Sema/SemaHLSL.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class SemaHLSL : public SemaBase {
6161
void handleParamModifierAttr(Decl *D, const ParsedAttr &AL);
6262

6363
bool CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall);
64+
65+
// HLSL Type trait implementations
66+
bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
6467
};
6568

6669
} // namespace clang

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "clang/Sema/Scope.h"
4040
#include "clang/Sema/ScopeInfo.h"
4141
#include "clang/Sema/SemaCUDA.h"
42+
#include "clang/Sema/SemaHLSL.h"
4243
#include "clang/Sema/SemaInternal.h"
4344
#include "clang/Sema/SemaLambda.h"
4445
#include "clang/Sema/SemaObjC.h"
@@ -6248,6 +6249,23 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceI
62486249
TSTToBeDeduced->getTemplateName().getAsTemplateDecl(), RhsT,
62496250
Info) == TemplateDeductionResult::Success;
62506251
}
6252+
case BTT_IsScalarizedLayoutCompatible: {
6253+
if (!LhsT->isVoidType() && !LhsT->isIncompleteArrayType() &&
6254+
Self.RequireCompleteType(Lhs->getTypeLoc().getBeginLoc(), LhsT,
6255+
diag::err_incomplete_type))
6256+
return true;
6257+
if (!RhsT->isVoidType() && !RhsT->isIncompleteArrayType() &&
6258+
Self.RequireCompleteType(Rhs->getTypeLoc().getBeginLoc(), RhsT,
6259+
diag::err_incomplete_type))
6260+
return true;
6261+
6262+
DiagnoseVLAInCXXTypeTrait(
6263+
Self, Lhs, tok::kw___builtin_hlsl_is_scalarized_layout_compatible);
6264+
DiagnoseVLAInCXXTypeTrait(
6265+
Self, Rhs, tok::kw___builtin_hlsl_is_scalarized_layout_compatible);
6266+
6267+
return Self.HLSL().IsScalarizedLayoutCompatible(LhsT, RhsT);
6268+
}
62516269
default:
62526270
llvm_unreachable("not a BTT");
62536271
}

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,3 +1524,85 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
15241524
}
15251525
return false;
15261526
}
1527+
1528+
static void BuildFlattenedTypeList(QualType BaseTy,
1529+
llvm::SmallVectorImpl<QualType> &List) {
1530+
llvm::SmallVector<QualType, 16> WorkList;
1531+
WorkList.push_back(BaseTy);
1532+
while (!WorkList.empty()) {
1533+
QualType T = WorkList.pop_back_val();
1534+
T = T.getCanonicalType().getUnqualifiedType();
1535+
assert(!isa<MatrixType>(T) && "Matrix types not yet supported in HLSL");
1536+
if (const auto *AT = dyn_cast<ConstantArrayType>(T)) {
1537+
llvm::SmallVector<QualType, 16> ElementFields;
1538+
// Generally I've avoided recursion in this algorithm, but arrays of
1539+
// structs could be time-consuming to flatten and churn through on the
1540+
// work list. Hopefully nesting arrays of structs containing arrays
1541+
// of structs too many levels deep is unlikely.
1542+
BuildFlattenedTypeList(AT->getElementType(), ElementFields);
1543+
// Repeat the element's field list n times.
1544+
for (uint64_t Ct = 0; Ct < AT->getZExtSize(); ++Ct)
1545+
List.insert(List.end(), ElementFields.begin(), ElementFields.end());
1546+
continue;
1547+
}
1548+
// Vectors can only have element types that are builtin types, so this can
1549+
// add directly to the list instead of to the WorkList.
1550+
if (const auto *VT = dyn_cast<VectorType>(T)) {
1551+
List.insert(List.end(), VT->getNumElements(), VT->getElementType());
1552+
continue;
1553+
}
1554+
if (const auto *RT = dyn_cast<RecordType>(T)) {
1555+
const RecordDecl *RD = RT->getDecl();
1556+
if (RD->isUnion()) {
1557+
List.push_back(T);
1558+
continue;
1559+
}
1560+
const CXXRecordDecl *CXXD = dyn_cast<CXXRecordDecl>(RD);
1561+
1562+
llvm::SmallVector<QualType, 16> FieldTypes;
1563+
if (CXXD && CXXD->isStandardLayout())
1564+
RD = CXXD->getStandardLayoutBaseWithFields();
1565+
1566+
for (const auto *FD : RD->fields())
1567+
FieldTypes.push_back(FD->getType());
1568+
// Reverse the newly added sub-range.
1569+
std::reverse(FieldTypes.begin(), FieldTypes.end());
1570+
WorkList.insert(WorkList.end(), FieldTypes.begin(), FieldTypes.end());
1571+
1572+
// If this wasn't a standard layout type we may also have some base
1573+
// classes to deal with.
1574+
if (CXXD && !CXXD->isStandardLayout()) {
1575+
FieldTypes.clear();
1576+
for (const auto &Base : CXXD->bases())
1577+
FieldTypes.push_back(Base.getType());
1578+
std::reverse(FieldTypes.begin(), FieldTypes.end());
1579+
WorkList.insert(WorkList.end(), FieldTypes.begin(), FieldTypes.end());
1580+
}
1581+
continue;
1582+
}
1583+
List.push_back(T);
1584+
}
1585+
}
1586+
1587+
bool SemaHLSL::IsScalarizedLayoutCompatible(QualType T1, QualType T2) const {
1588+
if (T1.isNull() || T2.isNull())
1589+
return false;
1590+
1591+
T1 = T1.getCanonicalType().getUnqualifiedType();
1592+
T2 = T2.getCanonicalType().getUnqualifiedType();
1593+
1594+
// If both types are the same canonical type, they're obviously compatible.
1595+
if (SemaRef.getASTContext().hasSameType(T1, T2))
1596+
return true;
1597+
1598+
llvm::SmallVector<QualType, 16> T1Types;
1599+
BuildFlattenedTypeList(T1, T1Types);
1600+
llvm::SmallVector<QualType, 16> T2Types;
1601+
BuildFlattenedTypeList(T2, T2Types);
1602+
1603+
// Check the flattened type list
1604+
return llvm::equal(T1Types, T2Types,
1605+
[this](QualType LHS, QualType RHS) -> bool {
1606+
return SemaRef.IsLayoutCompatible(LHS, RHS);
1607+
});
1608+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -verify %s
2+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -fnative-half-type -verify %s
3+
// expected-no-diagnostics
4+
5+
// Case 1: How many ways can I come up with to represent three float values?
6+
struct ThreeFloats1 {
7+
float X, Y, Z;
8+
};
9+
10+
struct ThreeFloats2 {
11+
float X[3];
12+
};
13+
14+
struct ThreeFloats3 {
15+
float3 V;
16+
};
17+
18+
struct ThreeFloats4 {
19+
float2 V;
20+
float F;
21+
};
22+
23+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(float3, float[3]), "");
24+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(float3, ThreeFloats1), "");
25+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(float3, ThreeFloats2), "");
26+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(float3, ThreeFloats3), "");
27+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(float3, ThreeFloats4), "");
28+
29+
// Case 2: structs and base classes and arrays, oh my!
30+
struct Dog {
31+
int Leg[4];
32+
bool Tail;
33+
float Fur;
34+
};
35+
36+
struct Shiba {
37+
int4 StubbyLegs;
38+
bool CurlyTail;
39+
struct Coating {
40+
float Fur;
41+
} F;
42+
};
43+
44+
struct FourLegged {
45+
int FR, FL, BR, BL;
46+
};
47+
48+
struct Doggo : FourLegged {
49+
bool WaggyBit;
50+
float Fuzz;
51+
};
52+
53+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Dog, Shiba), "");
54+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Dog, Doggo), "");
55+
56+
// Case 3: Arrays of structs inside structs
57+
58+
struct Cat {
59+
struct Leg {
60+
int L;
61+
} Legs[4];
62+
struct Other {
63+
bool Tail;
64+
float Furs;
65+
} Bits;
66+
};
67+
68+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Dog, Cat), "");
69+
70+
// case 4: Arrays of structs inside arrays of structs.
71+
struct Pets {
72+
Dog Puppers[6];
73+
Cat Kitties[4];
74+
};
75+
76+
struct Animals {
77+
Dog Puppers[2];
78+
Cat Kitties[8];
79+
};
80+
81+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Pets, Animals), "");
82+
83+
// Case 5: Turtles all the way down...
84+
85+
typedef int Turtle;
86+
87+
enum Ninja : Turtle {
88+
Leonardo,
89+
Donatello,
90+
Michelangelo,
91+
Raphael,
92+
};
93+
94+
enum NotNinja : Turtle {
95+
Fred,
96+
Mikey,
97+
};
98+
99+
enum Mammals : uint {
100+
Dog,
101+
Cat,
102+
};
103+
104+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Ninja, NotNinja), "");
105+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(Ninja, Mammals), "");
106+
107+
// Case 6: Some basic types.
108+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(int, int32_t), "");
109+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(uint, uint32_t), "");
110+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(int, uint), "");
111+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(int, float), "");
112+
113+
// Even though half and float may be the same size we don't want them to be
114+
// layout compatible since they are different types.
115+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(half, float), "");
116+
117+
// Case 6: Empty classes... because they're fun.
118+
119+
struct NotEmpty { int X; };
120+
struct Empty {};
121+
struct AlsoEmpty {};
122+
123+
struct DerivedEmpty : Empty {};
124+
125+
struct DerivedNotEmpty : Empty { int X; };
126+
struct DerivedEmptyNotEmptyBase : NotEmpty {};
127+
128+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Empty, AlsoEmpty), "");
129+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Empty, DerivedEmpty), "");
130+
131+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(NotEmpty, DerivedNotEmpty), "");
132+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(NotEmpty, DerivedEmptyNotEmptyBase), "");
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -verify %s
2+
3+
// Some things that don't work!
4+
5+
// Case 1: Both types must be complete!
6+
struct Defined {
7+
int X;
8+
};
9+
10+
11+
struct Undefined; // expected-note {{forward declaration of 'Undefined'}}
12+
13+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Undefined, Defined), ""); // expected-error{{incomplete type 'Undefined' where a complete type is required}}
14+
15+
// Case 2: No variable length arrays!
16+
17+
void fn(int X) {
18+
// expected-error@#vla {{variable length arrays are not supported for the current target}}
19+
// expected-error@#vla {{variable length arrays are not supported in '__builtin_hlsl_is_scalarized_layout_compatible'}}
20+
// expected-error@#vla {{static assertion failed due to requirement '__builtin_hlsl_is_scalarized_layout_compatible(int[4], int[X])'}}
21+
// expected-warning@#vla {{variable length arrays in C++ are a Clang extension}}
22+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(int[4], int[X]), ""); // #vla
23+
}
24+
25+
// Case 3: Make this always fail for unions.
26+
// HLSL doesn't really support unions, and the places where scalarized layouts
27+
// are valid is probably going to be really confusing for unions, so we should
28+
// just make sure unions are never scalarized compatible with anything other
29+
// than themselves.
30+
31+
union Wah {
32+
int OhNo;
33+
float NotAgain;
34+
};
35+
36+
struct OneInt {
37+
int I;
38+
};
39+
40+
struct OneFloat {
41+
float F;
42+
};
43+
44+
struct HasUnion {
45+
int I;
46+
Wah W;
47+
};
48+
49+
struct HasUnionSame {
50+
int I;
51+
Wah W;
52+
};
53+
54+
struct HasUnionDifferent {
55+
Wah W;
56+
int I;
57+
};
58+
59+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(Wah, Wah), "Identical types are always compatible");
60+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(Wah, OneInt), "Unions are not compatible with anything else");
61+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(Wah, OneFloat), "Unions are not compatible with anything else");
62+
63+
_Static_assert(__builtin_hlsl_is_scalarized_layout_compatible(HasUnion, HasUnionSame), "");
64+
_Static_assert(!__builtin_hlsl_is_scalarized_layout_compatible(HasUnion, HasUnionDifferent), "");

0 commit comments

Comments
 (0)