Skip to content

Commit 8be5654

Browse files
committed
[HLSL] Parameter modifier parsing and AST
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&)`.
1 parent 0e42df4 commit 8be5654

File tree

13 files changed

+227
-1
lines changed

13 files changed

+227
-1
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4229,6 +4229,18 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
42294229
let Documentation = [HLSLGroupSharedAddressSpaceDocs];
42304230
}
42314231

4232+
def HLSLParamModifier : TypeAttr {
4233+
let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">];
4234+
let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
4235+
Accessor<"isInOut", [CustomKeyword<"inout">]>,
4236+
Accessor<"isOut", [CustomKeyword<"out">]>,
4237+
Accessor<"isAnyOut", [CustomKeyword<"out">, CustomKeyword<"inout">]>,
4238+
Accessor<"isAnyIn", [CustomKeyword<"in">, CustomKeyword<"inout">]>];
4239+
let Subjects = SubjectList<[ParmVar]>;
4240+
let Documentation = [HLSLParamQualifierDocs];
4241+
let Args = [DefaultBoolArgument<"MergedSpelling", /*default*/0, /*fake*/1>];
4242+
}
4243+
42324244
def RandomizeLayout : InheritableAttr {
42334245
let Spellings = [GCC<"randomize_layout">];
42344246
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
@@ -11989,6 +11989,7 @@ def err_hlsl_numthreads_argument_oor : Error<"argument '%select{X|Y|Z}0' to numt
1198911989
def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed %0">;
1199011990
def err_hlsl_missing_numthreads : Error<"missing numthreads attribute for %0 shader entry">;
1199111991
def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">;
11992+
def err_hlsl_duplicate_parameter_modifier : Error<"duplicate parameter modifier %0">;
1199211993
def err_hlsl_missing_semantic_annotation : Error<
1199311994
"semantic annotations must be present for all parameters of an entry "
1199411995
"function or patch constant function">;
@@ -12004,6 +12005,9 @@ def err_hlsl_pointers_unsupported : Error<
1200412005
def err_hlsl_operator_unsupported : Error<
1200512006
"the '%select{&|*|->}0' operator is unsupported in HLSL">;
1200612007

12008+
def err_hlsl_param_qualifier_mismatch :
12009+
Error<"conflicting parameter qualifier %0 on parameter %1">;
12010+
1200712011
// Layout randomization diagnostics.
1200812012
def err_non_designated_init_used : Error<
1200912013
"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
@@ -626,6 +626,9 @@ KEYWORD(__noinline__ , KEYCUDA)
626626
KEYWORD(cbuffer , KEYHLSL)
627627
KEYWORD(tbuffer , KEYHLSL)
628628
KEYWORD(groupshared , KEYHLSL)
629+
KEYWORD(in , KEYHLSL)
630+
KEYWORD(inout , KEYHLSL)
631+
KEYWORD(out , KEYHLSL)
629632

630633
// OpenMP Type Traits
631634
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
@@ -3732,6 +3732,9 @@ class Sema final {
37323732
int X, int Y, int Z);
37333733
HLSLShaderAttr *mergeHLSLShaderAttr(Decl *D, const AttributeCommonInfo &AL,
37343734
HLSLShaderAttr::ShaderType ShaderType);
3735+
HLSLParamModifierAttr *
3736+
mergeHLSLParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
3737+
HLSLParamModifierAttr::Spelling Spelling);
37353738

37363739
void mergeDeclAttributes(NamedDecl *New, Decl *Old,
37373740
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
@@ -4528,6 +4528,9 @@ void Parser::ParseDeclarationSpecifiers(
45284528
break;
45294529

45304530
case tok::kw_groupshared:
4531+
case tok::kw_in:
4532+
case tok::kw_inout:
4533+
case tok::kw_out:
45314534
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
45324535
ParseHLSLQualifiers(DS.getAttributes());
45334536
continue;
@@ -5559,7 +5562,6 @@ bool Parser::isTypeSpecifierQualifier() {
55595562
case tok::kw___read_write:
55605563
case tok::kw___write_only:
55615564
case tok::kw___funcref:
5562-
case tok::kw_groupshared:
55635565
return true;
55645566

55655567
case tok::kw_private:
@@ -5568,6 +5570,13 @@ bool Parser::isTypeSpecifierQualifier() {
55685570
// C11 _Atomic
55695571
case tok::kw__Atomic:
55705572
return true;
5573+
5574+
// HLSL type qualifiers
5575+
case tok::kw_groupshared:
5576+
case tok::kw_in:
5577+
case tok::kw_inout:
5578+
case tok::kw_out:
5579+
return getLangOpts().HLSL;
55715580
}
55725581
}
55735582

@@ -6067,6 +6076,9 @@ void Parser::ParseTypeQualifierListOpt(
60676076
break;
60686077

60696078
case tok::kw_groupshared:
6079+
case tok::kw_in:
6080+
case tok::kw_inout:
6081+
case tok::kw_out:
60706082
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
60716083
ParseHLSLQualifiers(DS.getAttributes());
60726084
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
@@ -3368,6 +3368,26 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
33683368
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
33693369
}
33703370

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

clang/lib/Sema/SemaDeclAttr.cpp

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

7309+
static void handleHLSLParamModifierAttr(Sema &S, Decl *D,
7310+
const ParsedAttr &AL) {
7311+
HLSLParamModifierAttr *NewAttr = S.mergeHLSLParamModifierAttr(
7312+
D, AL,
7313+
static_cast<HLSLParamModifierAttr::Spelling>(AL.getSemanticSpelling()));
7314+
if (NewAttr)
7315+
D->addAttr(NewAttr);
7316+
}
7317+
7318+
HLSLParamModifierAttr *
7319+
Sema::mergeHLSLParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
7320+
HLSLParamModifierAttr::Spelling Spelling) {
7321+
// We can only merge an `in` attribute with an `out` attribute. All other
7322+
// combinations of duplicated attributes are ill-formed.
7323+
if (HLSLParamModifierAttr *PA = D->getAttr<HLSLParamModifierAttr>()) {
7324+
if ((PA->isIn() && Spelling == HLSLParamModifierAttr::Keyword_out) ||
7325+
(PA->isOut() && Spelling == HLSLParamModifierAttr::Keyword_in)) {
7326+
D->dropAttr<HLSLParamModifierAttr>();
7327+
SourceRange AdjustedRange = {PA->getLocation(), AL.getRange().getEnd()};
7328+
return HLSLParamModifierAttr::Create(
7329+
Context, /*MergedSpelling=*/true, AdjustedRange,
7330+
HLSLParamModifierAttr::Keyword_inout);
7331+
}
7332+
Diag(AL.getLoc(), diag::err_hlsl_duplicate_parameter_modifier) << AL;
7333+
Diag(PA->getLocation(), diag::note_conflicting_attribute);
7334+
return nullptr;
7335+
}
7336+
return HLSLParamModifierAttr::Create(Context, AL);
7337+
}
7338+
73097339
static void handleMSInheritanceAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
73107340
if (!S.LangOpts.CPlusPlus) {
73117341
S.Diag(AL.getLoc(), diag::err_attribute_not_supported_in_lang)
@@ -9456,6 +9486,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
94569486
case ParsedAttr::AT_HLSLResourceBinding:
94579487
handleHLSLResourceBindingAttr(S, D, AL);
94589488
break;
9489+
case ParsedAttr::AT_HLSLParamModifier:
9490+
handleHLSLParamModifierAttr(S, D, AL);
9491+
break;
94599492

94609493
case ParsedAttr::AT_AbiTag:
94619494
handleAbiTagAttr(S, D, AL);

clang/lib/Sema/SemaType.cpp

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

8664+
static void HandleHLSLParamModifierAttr(QualType &CurType,
8665+
const ParsedAttr &Attr, Sema &S) {
8666+
if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
8667+
Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out)
8668+
CurType = S.getASTContext().getLValueReferenceType(CurType);
8669+
}
8670+
86648671
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
86658672
TypeAttrLocation TAL,
86668673
const ParsedAttributesView &attrs,
@@ -8837,6 +8844,12 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
88378844
break;
88388845
}
88398846

8847+
case ParsedAttr::AT_HLSLParamModifier: {
8848+
HandleHLSLParamModifierAttr(type, attr, state.getSema());
8849+
attr.setUsedAsTypeAttr();
8850+
break;
8851+
}
8852+
88408853
MS_TYPE_ATTRS_CASELIST:
88418854
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
88428855
attr.setUsedAsTypeAttr();
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 %s -verify
2+
void fn(in out float f); // #fn
3+
4+
// expected-error@#fn2{{duplicate parameter modifier 'in'}}
5+
// expected-note@#fn2{{conflicting attribute is here}}
6+
void fn2(in in float f); // #fn2
7+
8+
// expected-error@#fn3{{duplicate parameter modifier 'out'}}
9+
// expected-note@#fn3{{conflicting attribute is here}}
10+
void fn3(out out float f); // #fn3
11+
12+
// expected-error@#fn4{{duplicate parameter modifier 'in'}}
13+
// expected-error@#fn4{{duplicate parameter modifier 'out'}}
14+
// expected-note@#fn4{{conflicting attribute is here}}
15+
// expected-note@#fn4{{conflicting attribute is here}}
16+
void fn4(inout in out float f); // #fn4
17+
18+
// expected-error@#fn5{{duplicate parameter modifier 'in'}}
19+
// expected-note@#fn5{{conflicting attribute is here}}
20+
void fn5(inout in float f); // #fn5
21+
22+
// expected-error@#fn6{{duplicate parameter modifier 'out'}}
23+
// expected-note@#fn6{{conflicting attribute is here}}
24+
void fn6(inout out float f); // #fn6
25+
26+
// expected-error@#fn-def{{conflicting parameter qualifier 'out' on parameter 'f'}}
27+
// expected-note@#fn{{previously declared as 'inout' here}}
28+
void fn(out float f) { // #fn-def
29+
f = 2;
30+
}
31+
32+
// Overload resolution failure.
33+
void fn(in float f); // #fn-in
34+
35+
void failOverloadResolution() {
36+
float f = 1.0;
37+
fn(f); // expected-error{{call to 'fn' is ambiguous}}
38+
// expected-note@#fn-def{{candidate function}}
39+
// expected-note@#fn-in{{candidate function}}
40+
}
41+
42+
// No errors on these scenarios.
43+
44+
// Alternating `inout` and `in out` spellings between declaration and
45+
// definitions is fine since they have the same semantic meaning.
46+
void fn7(inout float f);
47+
void fn7(in out float f) {}
48+
49+
void fn8(in out float f);
50+
void fn8(inout float f) {}
51+
52+
// These two declare two different functions (although calling them will be
53+
// ambiguous). This is equivalent to declaring a functiion that takes a
54+
// reference and a function that takes a value of the same type.
55+
void fn9(in float f);
56+
void fn9(out float f);
57+
58+
// The `in` attribute is effectively optional. If no attribute is present it is
59+
// the same as `in`, so these declarations match the functions.
60+
void fn10(in float f);
61+
void fn10(float f) {}
62+
63+
void fn11(float f);
64+
void fn11(in float f) {}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library %s -ast-dump | FileCheck %s
2+
3+
// CHECK: FunctionDecl {{.*}} fn 'void (float)'
4+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float'
5+
// CHECK-NOT: HLSLParamModifierAttr
6+
void fn(float f);
7+
8+
// CHECK: FunctionDecl {{.*}}6 fn2 'void (float)'
9+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float'
10+
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} in
11+
// CHECK-NOT: HLSLParamModifierAttr
12+
void fn2(in float f);
13+
14+
// CHECK: FunctionDecl {{.*}} fn3 'void (float &)'
15+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
16+
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
17+
// CHECK-NOT: HLSLParamModifierAttr
18+
void fn3(out float f);
19+
20+
// CHECK: FunctionDecl {{.*}} fn4 'void (float &)'
21+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
22+
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
23+
// CHECK-NOT: HLSLParamModifierAttr
24+
void fn4(inout float f);
25+
26+
// CHECK: FunctionDecl {{.*}} fn5 'void (float &)'
27+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
28+
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
29+
// CHECK-NOT: HLSLParamModifierAttr
30+
void fn5(out in float f);
31+
32+
// CHECK: FunctionDecl {{.*}} fn6 'void (float &)'
33+
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
34+
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
35+
// CHECK-NOT: HLSLParamModifierAttr
36+
void fn6(in out float f);

0 commit comments

Comments
 (0)