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/Basic/DiagnosticParse.h" |
16 | #include "clang/Parse/Parser.h" |
17 | #include "clang/Parse/RAIIObjectsForParser.h" |
18 | #include "clang/Sema/SemaHLSL.h" |
19 | |
20 | using namespace clang; |
21 | |
22 | static bool validateDeclsInsideHLSLBuffer(Parser::DeclGroupPtrTy DG, |
23 | SourceLocation BufferLoc, |
24 | bool IsCBuffer, Parser &P) { |
25 | // The parse is failed, just return false. |
26 | if (!DG) |
27 | return false; |
28 | DeclGroupRef Decls = DG.get(); |
29 | bool IsValid = true; |
30 | // Only allow function, variable, record, and empty decls inside HLSLBuffer. |
31 | for (DeclGroupRef::iterator I = Decls.begin(), E = Decls.end(); I != E; ++I) { |
32 | Decl *D = *I; |
33 | if (isa<CXXRecordDecl, RecordDecl, FunctionDecl, VarDecl, EmptyDecl>(Val: D)) |
34 | continue; |
35 | |
36 | // FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer. |
37 | if (isa<HLSLBufferDecl, NamespaceDecl>(Val: D)) { |
38 | P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer) |
39 | << IsCBuffer; |
40 | IsValid = false; |
41 | continue; |
42 | } |
43 | |
44 | IsValid = false; |
45 | P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer) |
46 | << IsCBuffer; |
47 | } |
48 | return IsValid; |
49 | } |
50 | |
51 | Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd) { |
52 | assert((Tok.is(tok::kw_cbuffer) || Tok.is(tok::kw_tbuffer)) && |
53 | "Not a cbuffer or tbuffer!" ); |
54 | bool IsCBuffer = Tok.is(K: tok::kw_cbuffer); |
55 | SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'. |
56 | |
57 | if (!Tok.is(K: tok::identifier)) { |
58 | Diag(Tok, diag::err_expected) << tok::identifier; |
59 | return nullptr; |
60 | } |
61 | |
62 | IdentifierInfo *Identifier = Tok.getIdentifierInfo(); |
63 | SourceLocation IdentifierLoc = ConsumeToken(); |
64 | |
65 | ParsedAttributes Attrs(AttrFactory); |
66 | MaybeParseHLSLAnnotations(Attrs, EndLoc: nullptr); |
67 | |
68 | ParseScope BufferScope(this, Scope::DeclScope); |
69 | BalancedDelimiterTracker T(*this, tok::l_brace); |
70 | if (T.consumeOpen()) { |
71 | Diag(Tok, diag::err_expected) << tok::l_brace; |
72 | return nullptr; |
73 | } |
74 | |
75 | Decl *D = Actions.HLSL().ActOnStartBuffer(BufferScope: getCurScope(), CBuffer: IsCBuffer, KwLoc: BufferLoc, |
76 | Ident: Identifier, IdentLoc: IdentifierLoc, |
77 | LBrace: T.getOpenLocation()); |
78 | Actions.ProcessDeclAttributeList(S: Actions.CurScope, D, AttrList: Attrs); |
79 | |
80 | while (Tok.isNot(K: tok::r_brace) && Tok.isNot(K: tok::eof)) { |
81 | // FIXME: support attribute on constants inside cbuffer/tbuffer. |
82 | ParsedAttributes DeclAttrs(AttrFactory); |
83 | ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); |
84 | |
85 | DeclGroupPtrTy Result = |
86 | ParseExternalDeclaration(DeclAttrs, DeclSpecAttrs&: EmptyDeclSpecAttrs); |
87 | if (!validateDeclsInsideHLSLBuffer(DG: Result, BufferLoc: IdentifierLoc, IsCBuffer, |
88 | P&: *this)) { |
89 | T.skipToEnd(); |
90 | DeclEnd = T.getCloseLocation(); |
91 | BufferScope.Exit(); |
92 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
93 | return nullptr; |
94 | } |
95 | } |
96 | |
97 | T.consumeClose(); |
98 | DeclEnd = T.getCloseLocation(); |
99 | BufferScope.Exit(); |
100 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
101 | |
102 | return D; |
103 | } |
104 | |
105 | static void fixSeparateAttrArgAndNumber(StringRef ArgStr, SourceLocation ArgLoc, |
106 | Token Tok, ArgsVector &ArgExprs, |
107 | Parser &P, ASTContext &Ctx, |
108 | Preprocessor &PP) { |
109 | StringRef Num = StringRef(Tok.getLiteralData(), Tok.getLength()); |
110 | SourceLocation EndNumLoc = Tok.getEndLoc(); |
111 | |
112 | P.ConsumeToken(); // consume constant. |
113 | std::string FixedArg = ArgStr.str() + Num.str(); |
114 | P.Diag(ArgLoc, diag::err_hlsl_separate_attr_arg_and_number) |
115 | << FixedArg |
116 | << FixItHint::CreateReplacement(SourceRange(ArgLoc, EndNumLoc), FixedArg); |
117 | ArgsUnion &Slot = ArgExprs.back(); |
118 | Slot = new (Ctx) IdentifierLoc(ArgLoc, PP.getIdentifierInfo(Name: FixedArg)); |
119 | } |
120 | |
121 | void Parser::ParseHLSLAnnotations(ParsedAttributes &Attrs, |
122 | SourceLocation *EndLoc, |
123 | bool CouldBeBitField) { |
124 | |
125 | assert(Tok.is(tok::colon) && "Not a HLSL Annotation" ); |
126 | Token OldToken = Tok; |
127 | ConsumeToken(); |
128 | |
129 | IdentifierInfo *II = nullptr; |
130 | if (Tok.is(K: tok::kw_register)) |
131 | II = PP.getIdentifierInfo(Name: "register" ); |
132 | else if (Tok.is(K: tok::identifier)) |
133 | II = Tok.getIdentifierInfo(); |
134 | |
135 | if (!II) { |
136 | if (CouldBeBitField) { |
137 | UnconsumeToken(Consumed&: OldToken); |
138 | return; |
139 | } |
140 | Diag(Tok.getLocation(), diag::err_expected_semantic_identifier); |
141 | return; |
142 | } |
143 | |
144 | SourceLocation Loc = ConsumeToken(); |
145 | if (EndLoc) |
146 | *EndLoc = Tok.getLocation(); |
147 | ParsedAttr::Kind AttrKind = |
148 | ParsedAttr::getParsedKind(Name: II, Scope: nullptr, SyntaxUsed: ParsedAttr::AS_HLSLAnnotation); |
149 | |
150 | ArgsVector ArgExprs; |
151 | switch (AttrKind) { |
152 | case ParsedAttr::AT_HLSLResourceBinding: { |
153 | if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after)) { |
154 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
155 | return; |
156 | } |
157 | if (!Tok.is(K: tok::identifier)) { |
158 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
159 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
160 | return; |
161 | } |
162 | StringRef SlotStr = Tok.getIdentifierInfo()->getName(); |
163 | SourceLocation SlotLoc = Tok.getLocation(); |
164 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
165 | |
166 | if (SlotStr.size() == 1) { |
167 | if (!Tok.is(K: tok::numeric_constant)) { |
168 | Diag(Tok.getLocation(), diag::err_expected) << tok::numeric_constant; |
169 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
170 | return; |
171 | } |
172 | // Add numeric_constant for fix-it. |
173 | fixSeparateAttrArgAndNumber(ArgStr: SlotStr, ArgLoc: SlotLoc, Tok, ArgExprs, P&: *this, |
174 | Ctx&: Actions.Context, PP); |
175 | } |
176 | if (Tok.is(K: tok::comma)) { |
177 | ConsumeToken(); // consume comma |
178 | if (!Tok.is(K: tok::identifier)) { |
179 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
180 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
181 | return; |
182 | } |
183 | StringRef SpaceStr = Tok.getIdentifierInfo()->getName(); |
184 | SourceLocation SpaceLoc = Tok.getLocation(); |
185 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
186 | |
187 | // Add numeric_constant for fix-it. |
188 | if (SpaceStr == "space" && Tok.is(K: tok::numeric_constant)) |
189 | fixSeparateAttrArgAndNumber(ArgStr: SpaceStr, ArgLoc: SpaceLoc, Tok, ArgExprs, P&: *this, |
190 | Ctx&: Actions.Context, PP); |
191 | } |
192 | if (ExpectAndConsume(tok::r_paren, diag::err_expected)) { |
193 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
194 | return; |
195 | } |
196 | } break; |
197 | case ParsedAttr::AT_HLSLPackOffset: { |
198 | // Parse 'packoffset( c[Subcomponent][.component] )'. |
199 | // Check '('. |
200 | if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after)) { |
201 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
202 | return; |
203 | } |
204 | // Check c[Subcomponent] as an identifier. |
205 | if (!Tok.is(K: tok::identifier)) { |
206 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
207 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
208 | return; |
209 | } |
210 | StringRef OffsetStr = Tok.getIdentifierInfo()->getName(); |
211 | SourceLocation SubComponentLoc = Tok.getLocation(); |
212 | if (OffsetStr[0] != 'c') { |
213 | Diag(Tok.getLocation(), diag::err_hlsl_packoffset_invalid_reg) |
214 | << OffsetStr; |
215 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
216 | return; |
217 | } |
218 | OffsetStr = OffsetStr.substr(Start: 1); |
219 | unsigned SubComponent = 0; |
220 | if (!OffsetStr.empty()) { |
221 | // Make sure SubComponent is a number. |
222 | if (OffsetStr.getAsInteger(Radix: 10, Result&: SubComponent)) { |
223 | Diag(SubComponentLoc.getLocWithOffset(1), |
224 | diag::err_hlsl_unsupported_register_number); |
225 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
226 | return; |
227 | } |
228 | } |
229 | unsigned Component = 0; |
230 | ConsumeToken(); // consume identifier. |
231 | SourceLocation ComponentLoc; |
232 | if (Tok.is(K: tok::period)) { |
233 | ConsumeToken(); // consume period. |
234 | if (!Tok.is(K: tok::identifier)) { |
235 | Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; |
236 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
237 | return; |
238 | } |
239 | StringRef ComponentStr = Tok.getIdentifierInfo()->getName(); |
240 | ComponentLoc = Tok.getLocation(); |
241 | ConsumeToken(); // consume identifier. |
242 | // Make sure Component is a single character. |
243 | if (ComponentStr.size() != 1) { |
244 | Diag(ComponentLoc, diag::err_hlsl_unsupported_component) |
245 | << ComponentStr; |
246 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
247 | return; |
248 | } |
249 | switch (ComponentStr[0]) { |
250 | case 'x': |
251 | case 'r': |
252 | Component = 0; |
253 | break; |
254 | case 'y': |
255 | case 'g': |
256 | Component = 1; |
257 | break; |
258 | case 'z': |
259 | case 'b': |
260 | Component = 2; |
261 | break; |
262 | case 'w': |
263 | case 'a': |
264 | Component = 3; |
265 | break; |
266 | default: |
267 | Diag(ComponentLoc, diag::err_hlsl_unsupported_component) |
268 | << ComponentStr; |
269 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
270 | return; |
271 | } |
272 | } |
273 | ASTContext &Ctx = Actions.getASTContext(); |
274 | QualType SizeTy = Ctx.getSizeType(); |
275 | uint64_t SizeTySize = Ctx.getTypeSize(T: SizeTy); |
276 | ArgExprs.push_back(IntegerLiteral::Create( |
277 | C: Ctx, V: llvm::APInt(SizeTySize, SubComponent), type: SizeTy, l: SubComponentLoc)); |
278 | ArgExprs.push_back(IntegerLiteral::Create( |
279 | C: Ctx, V: llvm::APInt(SizeTySize, Component), type: SizeTy, l: ComponentLoc)); |
280 | if (ExpectAndConsume(tok::r_paren, diag::err_expected)) { |
281 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
282 | return; |
283 | } |
284 | } break; |
285 | case ParsedAttr::UnknownAttribute: |
286 | Diag(Loc, diag::err_unknown_hlsl_semantic) << II; |
287 | return; |
288 | case ParsedAttr::AT_HLSLSV_GroupThreadID: |
289 | case ParsedAttr::AT_HLSLSV_GroupID: |
290 | case ParsedAttr::AT_HLSLSV_GroupIndex: |
291 | case ParsedAttr::AT_HLSLSV_DispatchThreadID: |
292 | case ParsedAttr::AT_HLSLSV_Position: |
293 | break; |
294 | default: |
295 | llvm_unreachable("invalid HLSL Annotation" ); |
296 | break; |
297 | } |
298 | |
299 | Attrs.addNew(attrName: II, attrRange: Loc, scopeName: nullptr, scopeLoc: SourceLocation(), args: ArgExprs.data(), |
300 | numArgs: ArgExprs.size(), form: ParsedAttr::Form::HLSLAnnotation()); |
301 | } |
302 | |