Skip to content

[HLSL] Parameter modifier parsing and AST #72139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -4229,6 +4229,18 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
let Documentation = [HLSLGroupSharedAddressSpaceDocs];
}

def HLSLParamModifier : TypeAttr {
let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">];
let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
Accessor<"isInOut", [CustomKeyword<"inout">]>,
Accessor<"isOut", [CustomKeyword<"out">]>,
Accessor<"isAnyOut", [CustomKeyword<"out">, CustomKeyword<"inout">]>,
Accessor<"isAnyIn", [CustomKeyword<"in">, CustomKeyword<"inout">]>];
let Subjects = SubjectList<[ParmVar]>;
let Documentation = [HLSLParamQualifierDocs];
let Args = [DefaultBoolArgument<"MergedSpelling", /*default*/0, /*fake*/1>];
}

def RandomizeLayout : InheritableAttr {
let Spellings = [GCC<"randomize_layout">];
let Subjects = SubjectList<[Record]>;
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -7061,6 +7061,25 @@ The full documentation is available here: https://learn.microsoft.com/en-us/wind
}];
}

def HLSLParamQualifierDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
HLSL function parameters are passed by value. Parameter declarations support
three qualifiers to denote parameter passing behavior. The three qualifiers are
`in`, `out` and `inout`.

Parameters annotated with `in` or with no annotation are passed by value from
the caller to the callee.

Parameters annotated with `out` are written to the argument after the callee
returns (Note: arguments values passed into `out` parameters _are not_ copied
into the callee).

Parameters annotated with `inout` are copied into the callee via a temporary,
and copied back to the argument after the callee returns.
}];
}

def AnnotateTypeDocs : Documentation {
let Category = DocCatType;
let Heading = "annotate_type";
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -11989,6 +11989,7 @@ def err_hlsl_numthreads_argument_oor : Error<"argument '%select{X|Y|Z}0' to numt
def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed %0">;
def err_hlsl_missing_numthreads : Error<"missing numthreads attribute for %0 shader entry">;
def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">;
def err_hlsl_duplicate_parameter_modifier : Error<"duplicate parameter modifier %0">;
def err_hlsl_missing_semantic_annotation : Error<
"semantic annotations must be present for all parameters of an entry "
"function or patch constant function">;
Expand All @@ -12004,6 +12005,9 @@ def err_hlsl_pointers_unsupported : Error<
def err_hlsl_operator_unsupported : Error<
"the '%select{&|*|->}0' operator is unsupported in HLSL">;

def err_hlsl_param_qualifier_mismatch :
Error<"conflicting parameter qualifier %0 on parameter %1">;

// Layout randomization diagnostics.
def err_non_designated_init_used : Error<
"a randomized struct can only be initialized with a designated initializer">;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,9 @@ KEYWORD(__noinline__ , KEYCUDA)
KEYWORD(cbuffer , KEYHLSL)
KEYWORD(tbuffer , KEYHLSL)
KEYWORD(groupshared , KEYHLSL)
KEYWORD(in , KEYHLSL)
KEYWORD(inout , KEYHLSL)
KEYWORD(out , KEYHLSL)

// OpenMP Type Traits
UNARY_EXPR_OR_TYPE_TRAIT(__builtin_omp_required_simd_align, OpenMPRequiredSimdAlign, KEYALL)
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3732,6 +3732,9 @@ class Sema final {
int X, int Y, int Z);
HLSLShaderAttr *mergeHLSLShaderAttr(Decl *D, const AttributeCommonInfo &AL,
HLSLShaderAttr::ShaderType ShaderType);
HLSLParamModifierAttr *
mergeHLSLParamModifierAttr(Decl *D, const AttributeCommonInfo &AL,
HLSLParamModifierAttr::Spelling Spelling);

void mergeDeclAttributes(NamedDecl *New, Decl *Old,
AvailabilityMergeKind AMK = AMK_Redeclaration);
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,10 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ArmMveStrictPolymorphism:
OS << "__clang_arm_mve_strict_polymorphism";
break;

// Nothing to print for this attribute.
case attr::HLSLParamModifier:
break;
}
OS << "))";
}
Expand Down
14 changes: 13 additions & 1 deletion clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4528,6 +4528,9 @@ void Parser::ParseDeclarationSpecifiers(
break;

case tok::kw_groupshared:
case tok::kw_in:
case tok::kw_inout:
case tok::kw_out:
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
ParseHLSLQualifiers(DS.getAttributes());
continue;
Expand Down Expand Up @@ -5559,7 +5562,6 @@ bool Parser::isTypeSpecifierQualifier() {
case tok::kw___read_write:
case tok::kw___write_only:
case tok::kw___funcref:
case tok::kw_groupshared:
return true;

case tok::kw_private:
Expand All @@ -5568,6 +5570,13 @@ bool Parser::isTypeSpecifierQualifier() {
// C11 _Atomic
case tok::kw__Atomic:
return true;

// HLSL type qualifiers
case tok::kw_groupshared:
case tok::kw_in:
case tok::kw_inout:
case tok::kw_out:
return getLangOpts().HLSL;
}
}

Expand Down Expand Up @@ -6067,6 +6076,9 @@ void Parser::ParseTypeQualifierListOpt(
break;

case tok::kw_groupshared:
case tok::kw_in:
case tok::kw_inout:
case tok::kw_out:
// NOTE: ParseHLSLQualifiers will consume the qualifier token.
ParseHLSLQualifiers(DS.getAttributes());
continue;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Parse/ParseTentative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,9 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,

// HLSL address space qualifiers
case tok::kw_groupshared:
case tok::kw_in:
case tok::kw_inout:
case tok::kw_out:

// GNU
case tok::kw_restrict:
Expand Down
20 changes: 20 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3368,6 +3368,26 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
}

// 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.
if (S.getLangOpts().HLSL) {
const auto *NDAttr = newDecl->getAttr<HLSLParamModifierAttr>();
const auto *ODAttr = oldDecl->getAttr<HLSLParamModifierAttr>();
// We don't need to cover the case where one declaration doesn't have an
// attribute. The only possible case there is if one declaration has an `in`
// attribute and the other declaration has no attribute. This case is
// allowed since parameters are `in` by default.
if (NDAttr && ODAttr &&
NDAttr->getSpellingListIndex() != ODAttr->getSpellingListIndex()) {
S.Diag(newDecl->getLocation(), diag::err_hlsl_param_qualifier_mismatch)
<< NDAttr << newDecl;
S.Diag(oldDecl->getLocation(), diag::note_previous_declaration_as)
<< ODAttr;
}
}

if (!oldDecl->hasAttrs())
return;

Expand Down
33 changes: 33 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7306,6 +7306,36 @@ static void handleHLSLResourceBindingAttr(Sema &S, Decl *D,
D->addAttr(NewAttr);
}

static void handleHLSLParamModifierAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
HLSLParamModifierAttr *NewAttr = S.mergeHLSLParamModifierAttr(
D, AL,
static_cast<HLSLParamModifierAttr::Spelling>(AL.getSemanticSpelling()));
if (NewAttr)
D->addAttr(NewAttr);
}

HLSLParamModifierAttr *
Sema::mergeHLSLParamModifierAttr(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(
Context, /*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(Context, AL);
}

static void handleMSInheritanceAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
if (!S.LangOpts.CPlusPlus) {
S.Diag(AL.getLoc(), diag::err_attribute_not_supported_in_lang)
Expand Down Expand Up @@ -9456,6 +9486,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_HLSLResourceBinding:
handleHLSLResourceBindingAttr(S, D, AL);
break;
case ParsedAttr::AT_HLSLParamModifier:
handleHLSLParamModifierAttr(S, D, AL);
break;

case ParsedAttr::AT_AbiTag:
handleAbiTagAttr(S, D, AL);
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,14 @@ static bool isRelevantAttr(Sema &S, const Decl *D, const Attr *A) {
return true;
}

static void instantiateDependentHLSLParamModifierAttr(
Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
const HLSLParamModifierAttr *Attr, Decl *New) {
ParmVarDecl *P = cast<ParmVarDecl>(New);
P->addAttr(Attr->clone(S.getASTContext()));
P->setType(S.getASTContext().getLValueReferenceType(P->getType()));
}

void Sema::InstantiateAttrsForDecl(
const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl,
Decl *New, LateInstantiatedAttrVec *LateAttrs,
Expand Down Expand Up @@ -784,6 +792,12 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
*AMDGPUFlatWorkGroupSize, New);
}

if (const auto *ParamAttr = dyn_cast<HLSLParamModifierAttr>(TmplAttr)) {
instantiateDependentHLSLParamModifierAttr(*this, TemplateArgs, ParamAttr,
New);
continue;
}

// Existing DLL attribute on the instantiation takes precedence.
if (TmplAttr->getKind() == attr::DLLExport ||
TmplAttr->getKind() == attr::DLLImport) {
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8661,6 +8661,17 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
}
}

static void HandleHLSLParamModifierAttr(QualType &CurType,
const ParsedAttr &Attr, Sema &S) {
// Don't apply this attribute to template dependent types. It is applied on
// substitution during template instantiation.
if (CurType->isDependentType())
return;
if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out)
CurType = S.getASTContext().getLValueReferenceType(CurType);
}

static void processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL,
const ParsedAttributesView &attrs,
Expand Down Expand Up @@ -8837,6 +8848,12 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
break;
}

case ParsedAttr::AT_HLSLParamModifier: {
HandleHLSLParamModifierAttr(type, attr, state.getSema());
attr.setUsedAsTypeAttr();
break;
}

MS_TYPE_ATTRS_CASELIST:
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
attr.setUsedAsTypeAttr();
Expand Down
12 changes: 12 additions & 0 deletions clang/test/ParserHLSL/hlsl_groupshared.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %clang_cc1 %s -verify
extern groupshared float f; // expected-error{{unknown type name 'groupshared'}}

extern float groupshared f2; // expected-error{{expected ';' after top level declarator}}

namespace {
float groupshared [[]] f3 = 12; // expected-error{{expected ';' after top level declarator}}
}

// expected-error@#fgc{{expected ';' after top level declarator}}
// expected-error@#fgc{{a type specifier is required for all declarations}}
float groupshared const f4 = 12; // #fgc
50 changes: 50 additions & 0 deletions clang/test/ParserHLSL/hlsl_parameter_modifiers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// RUN: %clang_cc1 %s -verify

// expected-error@#fn{{unknown type name 'in'}}
// expected-error@#fn{{expected ')'}}
// expected-note@#fn{{to match this '('}}
void fn(in out float f); // #fn

// expected-error@#fn2{{unknown type name 'in'}}
// expected-error@#fn2{{expected ')'}}
// expected-note@#fn2{{to match this '('}}
void fn2(in in float f); // #fn2

// expected-error@#fn3{{unknown type name 'out'}}
// expected-error@#fn3{{expected ')'}}
// expected-note@#fn3{{to match this '('}}
void fn3(out out float f); // #fn3

// expected-error@#fn4{{unknown type name 'inout'}}
// expected-error@#fn4{{expected ')'}}
// expected-note@#fn4{{to match this '('}}
void fn4(inout in out float f); // #fn4

// expected-error@#fn5{{unknown type name 'inout'}}
// expected-error@#fn5{{expected ')'}}
// expected-note@#fn5{{to match this '('}}
void fn5(inout in float f); // #fn5

// expected-error@#fn6{{unknown type name 'inout'}}
// expected-error@#fn6{{expected ')'}}
// expected-note@#fn6{{to match this '('}}
void fn6(inout out float f); // #fn6

void implicitFn(float f);

// expected-error@#inFn{{unknown type name 'in'}}
void inFn(in float f); // #inFn

// expected-error@#inoutFn{{unknown type name 'inout'}}
void inoutFn(inout float f); // #inoutFn

// expected-error@#outFn{{unknown type name 'out'}}
void outFn(out float f); // #outFn

// expected-error@#fn7{{unknown type name 'inout'}}
// expected-error@#fn7{{declaration of 'T' shadows template parameter}}
// expected-error@#fn7{{expected ')'}}
// expected-note@#fn7{{to match this '('}}
template <typename T> // expected-note{{template parameter is declared here}}
void fn7(inout T f); // #fn7

Loading