Skip to content

Commit d462621

Browse files
authored
[HLSL] Parameter modifier parsing and AST (#72139)
This change implements parsing for HLSL's parameter modifier keywords `in`, `out` and `inout`. Because HLSL doesn't support references or pointers, these keywords are used to allow parameters to be passed in and out of functions. This change only implements the parsing and AST support. In the HLSL ASTs we represent `out` and `inout` parameters as references, and we implement the semantics of by-value passing during IR generation. In HLSL parameters marked `out` and `inout` are ambiguous in function declarations, and `in`, `out` and `inout` may be ambiguous at call sites. This means a function may be defined as `fn(in T)` and `fn(inout T)` or `fn(out T)`, but not `fn(inout T)` and `fn(out T)`. If a funciton `fn` is declared with `in` and `inout` or `out` arguments, the call will be ambiguous the same as a C++ call would be ambiguous given declarations `fn(T)` and `fn(T&)`. Fixes #59849
1 parent ed27a4e commit d462621

16 files changed

+366
-1
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4253,6 +4253,18 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
42534253
let Documentation = [HLSLGroupSharedAddressSpaceDocs];
42544254
}
42554255

4256+
def HLSLParamModifier : TypeAttr {
4257+
let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">];
4258+
let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
4259+
Accessor<"isInOut", [CustomKeyword<"inout">]>,
4260+
Accessor<"isOut", [CustomKeyword<"out">]>,
4261+
Accessor<"isAnyOut", [CustomKeyword<"out">, CustomKeyword<"inout">]>,
4262+
Accessor<"isAnyIn", [CustomKeyword<"in">, CustomKeyword<"inout">]>];
4263+
let Subjects = SubjectList<[ParmVar]>;
4264+
let Documentation = [HLSLParamQualifierDocs];
4265+
let Args = [DefaultBoolArgument<"MergedSpelling", /*default*/0, /*fake*/1>];
4266+
}
4267+
42564268
def RandomizeLayout : InheritableAttr {
42574269
let Spellings = [GCC<"randomize_layout">];
42584270
let Subjects = SubjectList<[Record]>;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7061,6 +7061,25 @@ The full documentation is available here: https://learn.microsoft.com/en-us/wind
70617061
}];
70627062
}
70637063

7064+
def HLSLParamQualifierDocs : Documentation {
7065+
let Category = DocCatVariable;
7066+
let Content = [{
7067+
HLSL function parameters are passed by value. Parameter declarations support
7068+
three qualifiers to denote parameter passing behavior. The three qualifiers are
7069+
`in`, `out` and `inout`.
7070+
7071+
Parameters annotated with `in` or with no annotation are passed by value from
7072+
the caller to the callee.
7073+
7074+
Parameters annotated with `out` are written to the argument after the callee
7075+
returns (Note: arguments values passed into `out` parameters _are not_ copied
7076+
into the callee).
7077+
7078+
Parameters annotated with `inout` are copied into the callee via a temporary,
7079+
and copied back to the argument after the callee returns.
7080+
}];
7081+
}
7082+
70647083
def AnnotateTypeDocs : Documentation {
70657084
let Category = DocCatType;
70667085
let Heading = "annotate_type";

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12004,6 +12004,7 @@ def err_hlsl_numthreads_argument_oor : Error<"argument '%select{X|Y|Z}0' to numt
1200412004
def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed %0">;
1200512005
def err_hlsl_missing_numthreads : Error<"missing numthreads attribute for %0 shader entry">;
1200612006
def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">;
12007+
def err_hlsl_duplicate_parameter_modifier : Error<"duplicate parameter modifier %0">;
1200712008
def err_hlsl_missing_semantic_annotation : Error<
1200812009
"semantic annotations must be present for all parameters of an entry "
1200912010
"function or patch constant function">;
@@ -12019,6 +12020,9 @@ def err_hlsl_pointers_unsupported : Error<
1201912020
def err_hlsl_operator_unsupported : Error<
1202012021
"the '%select{&|*|->}0' operator is unsupported in HLSL">;
1202112022

12023+
def err_hlsl_param_qualifier_mismatch :
12024+
Error<"conflicting parameter qualifier %0 on parameter %1">;
12025+
1202212026
// Layout randomization diagnostics.
1202312027
def err_non_designated_init_used : Error<
1202412028
"a randomized struct can only be initialized with a designated initializer">;

clang/include/clang/Basic/TokenKinds.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,9 @@ KEYWORD(__noinline__ , KEYCUDA)
628628
KEYWORD(cbuffer , KEYHLSL)
629629
KEYWORD(tbuffer , KEYHLSL)
630630
KEYWORD(groupshared , KEYHLSL)
631+
KEYWORD(in , KEYHLSL)
632+
KEYWORD(inout , KEYHLSL)
633+
KEYWORD(out , KEYHLSL)
631634

632635
// OpenMP Type Traits
633636
UNARY_EXPR_OR_TYPE_TRAIT(__builtin_omp_required_simd_align, OpenMPRequiredSimdAlign, KEYALL)

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3737,6 +3737,9 @@ class Sema final {
37373737
int X, int Y, int Z);
37383738
HLSLShaderAttr *mergeHLSLShaderAttr(Decl *D, const AttributeCommonInfo &AL,
37393739
HLSLShaderAttr::ShaderType ShaderType);
3740+
HLSLParamModifierAttr *
3741+
mergeHLSLParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
3742+
HLSLParamModifierAttr::Spelling Spelling);
37403743

37413744
void mergeDeclAttributes(NamedDecl *New, Decl *Old,
37423745
AvailabilityMergeKind AMK = AMK_Redeclaration);

clang/lib/AST/TypePrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,10 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
18941894
case attr::ArmMveStrictPolymorphism:
18951895
OS << "__clang_arm_mve_strict_polymorphism";
18961896
break;
1897+
1898+
// Nothing to print for this attribute.
1899+
case attr::HLSLParamModifier:
1900+
break;
18971901
}
18981902
OS << "))";
18991903
}

clang/lib/Parse/ParseDecl.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4514,6 +4514,9 @@ void Parser::ParseDeclarationSpecifiers(
45144514
break;
45154515

45164516
case tok::kw_groupshared:
4517+
case tok::kw_in:
4518+
case tok::kw_inout:
4519+
case tok::kw_out:
45174520
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
45184521
ParseHLSLQualifiers(DS.getAttributes());
45194522
continue;
@@ -5550,7 +5553,6 @@ bool Parser::isTypeSpecifierQualifier() {
55505553
case tok::kw___read_write:
55515554
case tok::kw___write_only:
55525555
case tok::kw___funcref:
5553-
case tok::kw_groupshared:
55545556
return true;
55555557

55565558
case tok::kw_private:
@@ -5559,6 +5561,13 @@ bool Parser::isTypeSpecifierQualifier() {
55595561
// C11 _Atomic
55605562
case tok::kw__Atomic:
55615563
return true;
5564+
5565+
// HLSL type qualifiers
5566+
case tok::kw_groupshared:
5567+
case tok::kw_in:
5568+
case tok::kw_inout:
5569+
case tok::kw_out:
5570+
return getLangOpts().HLSL;
55625571
}
55635572
}
55645573

@@ -6058,6 +6067,9 @@ void Parser::ParseTypeQualifierListOpt(
60586067
break;
60596068

60606069
case tok::kw_groupshared:
6070+
case tok::kw_in:
6071+
case tok::kw_inout:
6072+
case tok::kw_out:
60616073
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
60626074
ParseHLSLQualifiers(DS.getAttributes());
60636075
continue;

clang/lib/Parse/ParseTentative.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,9 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
15291529

15301530
// HLSL address space qualifiers
15311531
case tok::kw_groupshared:
1532+
case tok::kw_in:
1533+
case tok::kw_inout:
1534+
case tok::kw_out:
15321535

15331536
// GNU
15341537
case tok::kw_restrict:

clang/lib/Sema/SemaDecl.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,6 +3373,26 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
33733373
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
33743374
}
33753375

3376+
// HLSL parameter declarations for inout and out must match between
3377+
// declarations. In HLSL inout and out are ambiguous at the call site, but
3378+
// have different calling behavior, so you cannot overload a method based on a
3379+
// difference between inout and out annotations.
3380+
if (S.getLangOpts().HLSL) {
3381+
const auto *NDAttr = newDecl->getAttr<HLSLParamModifierAttr>();
3382+
const auto *ODAttr = oldDecl->getAttr<HLSLParamModifierAttr>();
3383+
// We don't need to cover the case where one declaration doesn't have an
3384+
// attribute. The only possible case there is if one declaration has an `in`
3385+
// attribute and the other declaration has no attribute. This case is
3386+
// allowed since parameters are `in` by default.
3387+
if (NDAttr && ODAttr &&
3388+
NDAttr->getSpellingListIndex() != ODAttr->getSpellingListIndex()) {
3389+
S.Diag(newDecl->getLocation(), diag::err_hlsl_param_qualifier_mismatch)
3390+
<< NDAttr << newDecl;
3391+
S.Diag(oldDecl->getLocation(), diag::note_previous_declaration_as)
3392+
<< ODAttr;
3393+
}
3394+
}
3395+
33763396
if (!oldDecl->hasAttrs())
33773397
return;
33783398

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7311,6 +7311,36 @@ static void handleHLSLResourceBindingAttr(Sema &S, Decl *D,
73117311
D->addAttr(NewAttr);
73127312
}
73137313

7314+
static void handleHLSLParamModifierAttr(Sema &S, Decl *D,
7315+
const ParsedAttr &AL) {
7316+
HLSLParamModifierAttr *NewAttr = S.mergeHLSLParamModifierAttr(
7317+
D, AL,
7318+
static_cast<HLSLParamModifierAttr::Spelling>(AL.getSemanticSpelling()));
7319+
if (NewAttr)
7320+
D->addAttr(NewAttr);
7321+
}
7322+
7323+
HLSLParamModifierAttr *
7324+
Sema::mergeHLSLParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
7325+
HLSLParamModifierAttr::Spelling Spelling) {
7326+
// We can only merge an `in` attribute with an `out` attribute. All other
7327+
// combinations of duplicated attributes are ill-formed.
7328+
if (HLSLParamModifierAttr *PA = D->getAttr<HLSLParamModifierAttr>()) {
7329+
if ((PA->isIn() && Spelling == HLSLParamModifierAttr::Keyword_out) ||
7330+
(PA->isOut() && Spelling == HLSLParamModifierAttr::Keyword_in)) {
7331+
D->dropAttr<HLSLParamModifierAttr>();
7332+
SourceRange AdjustedRange = {PA->getLocation(), AL.getRange().getEnd()};
7333+
return HLSLParamModifierAttr::Create(
7334+
Context, /*MergedSpelling=*/true, AdjustedRange,
7335+
HLSLParamModifierAttr::Keyword_inout);
7336+
}
7337+
Diag(AL.getLoc(), diag::err_hlsl_duplicate_parameter_modifier) << AL;
7338+
Diag(PA->getLocation(), diag::note_conflicting_attribute);
7339+
return nullptr;
7340+
}
7341+
return HLSLParamModifierAttr::Create(Context, AL);
7342+
}
7343+
73147344
static void handleMSInheritanceAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
73157345
if (!S.LangOpts.CPlusPlus) {
73167346
S.Diag(AL.getLoc(), diag::err_attribute_not_supported_in_lang)
@@ -9461,6 +9491,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
94619491
case ParsedAttr::AT_HLSLResourceBinding:
94629492
handleHLSLResourceBindingAttr(S, D, AL);
94639493
break;
9494+
case ParsedAttr::AT_HLSLParamModifier:
9495+
handleHLSLParamModifierAttr(S, D, AL);
9496+
break;
94649497

94659498
case ParsedAttr::AT_AbiTag:
94669499
handleAbiTagAttr(S, D, AL);

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,14 @@ static bool isRelevantAttr(Sema &S, const Decl *D, const Attr *A) {
663663
return true;
664664
}
665665

666+
static void instantiateDependentHLSLParamModifierAttr(
667+
Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
668+
const HLSLParamModifierAttr *Attr, Decl *New) {
669+
ParmVarDecl *P = cast<ParmVarDecl>(New);
670+
P->addAttr(Attr->clone(S.getASTContext()));
671+
P->setType(S.getASTContext().getLValueReferenceType(P->getType()));
672+
}
673+
666674
void Sema::InstantiateAttrsForDecl(
667675
const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl,
668676
Decl *New, LateInstantiatedAttrVec *LateAttrs,
@@ -784,6 +792,12 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
784792
*AMDGPUFlatWorkGroupSize, New);
785793
}
786794

795+
if (const auto *ParamAttr = dyn_cast<HLSLParamModifierAttr>(TmplAttr)) {
796+
instantiateDependentHLSLParamModifierAttr(*this, TemplateArgs, ParamAttr,
797+
New);
798+
continue;
799+
}
800+
787801
// Existing DLL attribute on the instantiation takes precedence.
788802
if (TmplAttr->getKind() == attr::DLLExport ||
789803
TmplAttr->getKind() == attr::DLLImport) {

clang/lib/Sema/SemaType.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8661,6 +8661,17 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
86618661
}
86628662
}
86638663

8664+
static void HandleHLSLParamModifierAttr(QualType &CurType,
8665+
const ParsedAttr &Attr, Sema &S) {
8666+
// Don't apply this attribute to template dependent types. It is applied on
8667+
// substitution during template instantiation.
8668+
if (CurType->isDependentType())
8669+
return;
8670+
if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
8671+
Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out)
8672+
CurType = S.getASTContext().getLValueReferenceType(CurType);
8673+
}
8674+
86648675
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
86658676
TypeAttrLocation TAL,
86668677
const ParsedAttributesView &attrs,
@@ -8837,6 +8848,12 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
88378848
break;
88388849
}
88398850

8851+
case ParsedAttr::AT_HLSLParamModifier: {
8852+
HandleHLSLParamModifierAttr(type, attr, state.getSema());
8853+
attr.setUsedAsTypeAttr();
8854+
break;
8855+
}
8856+
88408857
MS_TYPE_ATTRS_CASELIST:
88418858
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
88428859
attr.setUsedAsTypeAttr();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %clang_cc1 %s -verify
2+
extern groupshared float f; // expected-error{{unknown type name 'groupshared'}}
3+
4+
extern float groupshared f2; // expected-error{{expected ';' after top level declarator}}
5+
6+
namespace {
7+
float groupshared [[]] f3 = 12; // expected-error{{expected ';' after top level declarator}}
8+
}
9+
10+
// expected-error@#fgc{{expected ';' after top level declarator}}
11+
// expected-error@#fgc{{a type specifier is required for all declarations}}
12+
float groupshared const f4 = 12; // #fgc
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clang_cc1 %s -verify
2+
3+
// expected-error@#fn{{unknown type name 'in'}}
4+
// expected-error@#fn{{expected ')'}}
5+
// expected-note@#fn{{to match this '('}}
6+
void fn(in out float f); // #fn
7+
8+
// expected-error@#fn2{{unknown type name 'in'}}
9+
// expected-error@#fn2{{expected ')'}}
10+
// expected-note@#fn2{{to match this '('}}
11+
void fn2(in in float f); // #fn2
12+
13+
// expected-error@#fn3{{unknown type name 'out'}}
14+
// expected-error@#fn3{{expected ')'}}
15+
// expected-note@#fn3{{to match this '('}}
16+
void fn3(out out float f); // #fn3
17+
18+
// expected-error@#fn4{{unknown type name 'inout'}}
19+
// expected-error@#fn4{{expected ')'}}
20+
// expected-note@#fn4{{to match this '('}}
21+
void fn4(inout in out float f); // #fn4
22+
23+
// expected-error@#fn5{{unknown type name 'inout'}}
24+
// expected-error@#fn5{{expected ')'}}
25+
// expected-note@#fn5{{to match this '('}}
26+
void fn5(inout in float f); // #fn5
27+
28+
// expected-error@#fn6{{unknown type name 'inout'}}
29+
// expected-error@#fn6{{expected ')'}}
30+
// expected-note@#fn6{{to match this '('}}
31+
void fn6(inout out float f); // #fn6
32+
33+
void implicitFn(float f);
34+
35+
// expected-error@#inFn{{unknown type name 'in'}}
36+
void inFn(in float f); // #inFn
37+
38+
// expected-error@#inoutFn{{unknown type name 'inout'}}
39+
void inoutFn(inout float f); // #inoutFn
40+
41+
// expected-error@#outFn{{unknown type name 'out'}}
42+
void outFn(out float f); // #outFn
43+
44+
// expected-error@#fn7{{unknown type name 'inout'}}
45+
// expected-error@#fn7{{declaration of 'T' shadows template parameter}}
46+
// expected-error@#fn7{{expected ')'}}
47+
// expected-note@#fn7{{to match this '('}}
48+
template <typename T> // expected-note{{template parameter is declared here}}
49+
void fn7(inout T f); // #fn7
50+

0 commit comments

Comments
 (0)