1 | //===--- ParseHLSL.cpp - HLSL-specific parsing support --------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file implements the parsing logic for HLSL language features. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "clang/AST/Attr.h" |
14 | #include "clang/Basic/AttributeCommonInfo.h" |
15 | #include "clang/Parse/ParseDiagnostic.h" |
16 | #include "clang/Parse/Parser.h" |
17 | #include "clang/Parse/RAIIObjectsForParser.h" |
18 | |
19 | using namespace clang; |
20 | |
21 | static bool validateDeclsInsideHLSLBuffer(Parser::DeclGroupPtrTy DG, |
22 | SourceLocation BufferLoc, |
23 | bool IsCBuffer, Parser &P) { |
24 | // The parse is failed, just return false. |
25 | if (!DG) |
26 | return false; |
27 | DeclGroupRef Decls = DG.get(); |
28 | bool IsValid = true; |
29 | // Only allow function, variable, record decls inside HLSLBuffer. |
30 | for (DeclGroupRef::iterator I = Decls.begin(), E = Decls.end(); I != E; ++I) { |
31 | Decl *D = *I; |
32 | if (isa<CXXRecordDecl, RecordDecl, FunctionDecl, VarDecl>(Val: D)) |
33 | continue; |
34 | |
35 | // FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer. |
36 | if (isa<HLSLBufferDecl, NamespaceDecl>(Val: D)) { |
37 | P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer) |
38 | << IsCBuffer; |
39 | IsValid = false; |
40 | continue; |
41 | } |
42 | |
43 | IsValid = false; |
44 | P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer) |
45 | << IsCBuffer; |
46 | } |
47 | return IsValid; |
48 | } |
49 | |
50 | Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd) { |
51 | assert((Tok.is(tok::kw_cbuffer) || Tok.is(tok::kw_tbuffer)) && |
52 | "Not a cbuffer or tbuffer!" ); |
53 | bool IsCBuffer = Tok.is(K: tok::kw_cbuffer); |
54 | SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'. |
55 | |
56 | if (!Tok.is(K: tok::identifier)) { |
57 | Diag(Tok, diag::err_expected) << tok::identifier; |
58 | return nullptr; |
59 | } |
60 | |
61 | IdentifierInfo *Identifier = Tok.getIdentifierInfo(); |
62 | SourceLocation IdentifierLoc = ConsumeToken(); |
63 | |
64 | ParsedAttributes Attrs(AttrFactory); |
65 | MaybeParseHLSLSemantics(Attrs, EndLoc: nullptr); |
66 | |
67 | ParseScope BufferScope(this, Scope::DeclScope); |
68 | BalancedDelimiterTracker T(*this, tok::l_brace); |
69 | if (T.consumeOpen()) { |
70 | Diag(Tok, diag::err_expected) << tok::l_brace; |
71 | return nullptr; |
72 | } |
73 | |
74 | Decl *D = Actions.ActOnStartHLSLBuffer(BufferScope: getCurScope(), CBuffer: IsCBuffer, KwLoc: BufferLoc, |
75 | Ident: Identifier, IdentLoc: IdentifierLoc, |
76 | LBrace: T.getOpenLocation()); |
77 | |
78 | while (Tok.isNot(K: tok::r_brace) && Tok.isNot(K: tok::eof)) { |
79 | // FIXME: support attribute on constants inside cbuffer/tbuffer. |
80 | ParsedAttributes DeclAttrs(AttrFactory); |
81 | ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); |
82 | |
83 | DeclGroupPtrTy Result = |
84 | ParseExternalDeclaration(DeclAttrs, DeclSpecAttrs&: EmptyDeclSpecAttrs); |
85 | if (!validateDeclsInsideHLSLBuffer(DG: Result, BufferLoc: IdentifierLoc, IsCBuffer, |
86 | P&: *this)) { |
87 | T.skipToEnd(); |
88 | DeclEnd = T.getCloseLocation(); |
89 | BufferScope.Exit(); |
90 | Actions.ActOnFinishHLSLBuffer(Dcl: D, RBrace: DeclEnd); |
91 | return nullptr; |
92 | } |
93 | } |
94 | |
95 | T.consumeClose(); |
96 | DeclEnd = T.getCloseLocation(); |
97 | BufferScope.Exit(); |
98 | Actions.ActOnFinishHLSLBuffer(Dcl: D, RBrace: DeclEnd); |
99 | |
100 | Actions.ProcessDeclAttributeList(S: Actions.CurScope, D, AttrList: Attrs); |
101 | return D; |
102 | } |
103 | |
104 | static void fixSeparateAttrArgAndNumber(StringRef ArgStr, SourceLocation ArgLoc, |
105 | Token Tok, ArgsVector &ArgExprs, |
106 | Parser &P, ASTContext &Ctx, |
107 | Preprocessor &PP) { |
108 | StringRef Num = StringRef(Tok.getLiteralData(), Tok.getLength()); |
109 | SourceLocation EndNumLoc = Tok.getEndLoc(); |
110 | |
111 | P.ConsumeToken(); // consume constant. |
112 | std::string FixedArg = ArgStr.str() + Num.str(); |
113 | P.Diag(ArgLoc, diag::err_hlsl_separate_attr_arg_and_number) |
114 | << FixedArg |
115 | << FixItHint::CreateReplacement(SourceRange(ArgLoc, EndNumLoc), FixedArg); |
116 | ArgsUnion &Slot = ArgExprs.back(); |
117 | Slot = IdentifierLoc::create(Ctx, Loc: ArgLoc, Ident: PP.getIdentifierInfo(Name: FixedArg)); |
118 | } |
119 | |
120 | void Parser::ParseHLSLSemantics(ParsedAttributes &Attrs, |
121 | SourceLocation *EndLoc) { |
122 | // FIXME: HLSLSemantic is shared for Semantic and resource binding which is |
123 | // confusing. Need a better name to avoid misunderstanding. Issue |
124 | // https://github.com/llvm/llvm-project/issues/57882 |
125 | assert(Tok.is(tok::colon) && "Not a HLSL Semantic" ); |
126 | ConsumeToken(); |
127 | |
128 | IdentifierInfo *II = nullptr; |
129 | if (Tok.is(K: tok::kw_register)) |
130 | II = PP.getIdentifierInfo(Name: "register" ); |
131 | else if (Tok.is(K: tok::identifier)) |
132 | II = Tok.getIdentifierInfo(); |
133 | |
134 | if (!II) { |
135 | Diag(Tok.getLocation(), diag::err_expected_semantic_identifier); |
136 | return; |
137 | } |
138 | |
139 | SourceLocation Loc = ConsumeToken(); |
140 | if (EndLoc) |
141 | *EndLoc = Tok.getLocation(); |
142 | ParsedAttr::Kind AttrKind = |
143 | ParsedAttr::getParsedKind(Name: II, Scope: nullptr, SyntaxUsed: ParsedAttr::AS_HLSLSemantic); |
144 | |
145 | ArgsVector ArgExprs; |
146 | switch (AttrKind) { |
147 | case ParsedAttr::AT_HLSLResourceBinding: { |
148 | if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after)) { |
149 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
150 | return; |
151 | } |
152 | if (!Tok.is(K: tok::identifier)) { |
153 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
154 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
155 | return; |
156 | } |
157 | StringRef SlotStr = Tok.getIdentifierInfo()->getName(); |
158 | SourceLocation SlotLoc = Tok.getLocation(); |
159 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
160 | |
161 | // Add numeric_constant for fix-it. |
162 | if (SlotStr.size() == 1 && Tok.is(K: tok::numeric_constant)) |
163 | fixSeparateAttrArgAndNumber(ArgStr: SlotStr, ArgLoc: SlotLoc, Tok, ArgExprs, P&: *this, |
164 | Ctx&: Actions.Context, PP); |
165 | |
166 | if (Tok.is(K: tok::comma)) { |
167 | ConsumeToken(); // consume comma |
168 | if (!Tok.is(K: tok::identifier)) { |
169 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
170 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
171 | return; |
172 | } |
173 | StringRef SpaceStr = Tok.getIdentifierInfo()->getName(); |
174 | SourceLocation SpaceLoc = Tok.getLocation(); |
175 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
176 | |
177 | // Add numeric_constant for fix-it. |
178 | if (SpaceStr.equals(RHS: "space" ) && Tok.is(K: tok::numeric_constant)) |
179 | fixSeparateAttrArgAndNumber(ArgStr: SpaceStr, ArgLoc: SpaceLoc, Tok, ArgExprs, P&: *this, |
180 | Ctx&: Actions.Context, PP); |
181 | } |
182 | if (ExpectAndConsume(tok::r_paren, diag::err_expected)) { |
183 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
184 | return; |
185 | } |
186 | } break; |
187 | case ParsedAttr::UnknownAttribute: |
188 | Diag(Loc, diag::err_unknown_hlsl_semantic) << II; |
189 | return; |
190 | case ParsedAttr::AT_HLSLSV_GroupIndex: |
191 | case ParsedAttr::AT_HLSLSV_DispatchThreadID: |
192 | break; |
193 | default: |
194 | llvm_unreachable("invalid HLSL Semantic" ); |
195 | break; |
196 | } |
197 | |
198 | Attrs.addNew(attrName: II, attrRange: Loc, scopeName: nullptr, scopeLoc: SourceLocation(), args: ArgExprs.data(), |
199 | numArgs: ArgExprs.size(), form: ParsedAttr::Form::HLSLSemantic()); |
200 | } |
201 | |