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 | #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 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>(Val: D)) |
34 | continue; |
35 | |
36 | // FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer. |
37 | if (isa<HLSLBufferDecl, NamespaceDecl>(Val: D)) { |
38 | P.Diag(Loc: D->getLocation(), DiagID: diag::err_invalid_declaration_in_hlsl_buffer) |
39 | << IsCBuffer; |
40 | IsValid = false; |
41 | continue; |
42 | } |
43 | |
44 | IsValid = false; |
45 | P.Diag(Loc: D->getLocation(), DiagID: 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, DiagID: 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, DiagID: 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 | |
79 | while (Tok.isNot(K: tok::r_brace) && Tok.isNot(K: tok::eof)) { |
80 | // FIXME: support attribute on constants inside cbuffer/tbuffer. |
81 | ParsedAttributes DeclAttrs(AttrFactory); |
82 | ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); |
83 | |
84 | DeclGroupPtrTy Result = |
85 | ParseExternalDeclaration(DeclAttrs, DeclSpecAttrs&: EmptyDeclSpecAttrs); |
86 | if (!validateDeclsInsideHLSLBuffer(DG: Result, BufferLoc: IdentifierLoc, IsCBuffer, |
87 | P&: *this)) { |
88 | T.skipToEnd(); |
89 | DeclEnd = T.getCloseLocation(); |
90 | BufferScope.Exit(); |
91 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
92 | return nullptr; |
93 | } |
94 | } |
95 | |
96 | T.consumeClose(); |
97 | DeclEnd = T.getCloseLocation(); |
98 | BufferScope.Exit(); |
99 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
100 | |
101 | Actions.ProcessDeclAttributeList(S: Actions.CurScope, D, AttrList: Attrs); |
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(Loc: ArgLoc, DiagID: diag::err_hlsl_separate_attr_arg_and_number) |
115 | << FixedArg |
116 | << FixItHint::CreateReplacement(RemoveRange: SourceRange(ArgLoc, EndNumLoc), Code: FixedArg); |
117 | ArgsUnion &Slot = ArgExprs.back(); |
118 | Slot = IdentifierLoc::create(Ctx, Loc: ArgLoc, Ident: 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(Loc: Tok.getLocation(), DiagID: 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(ExpectedTok: tok::l_paren, Diag: 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(Loc: Tok.getLocation(), DiagID: 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 | // Add numeric_constant for fix-it. |
167 | if (SlotStr.size() == 1 && Tok.is(K: tok::numeric_constant)) |
168 | fixSeparateAttrArgAndNumber(ArgStr: SlotStr, ArgLoc: SlotLoc, Tok, ArgExprs, P&: *this, |
169 | Ctx&: Actions.Context, PP); |
170 | |
171 | if (Tok.is(K: tok::comma)) { |
172 | ConsumeToken(); // consume comma |
173 | if (!Tok.is(K: tok::identifier)) { |
174 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
175 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
176 | return; |
177 | } |
178 | StringRef SpaceStr = Tok.getIdentifierInfo()->getName(); |
179 | SourceLocation SpaceLoc = Tok.getLocation(); |
180 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
181 | |
182 | // Add numeric_constant for fix-it. |
183 | if (SpaceStr == "space" && Tok.is(K: tok::numeric_constant)) |
184 | fixSeparateAttrArgAndNumber(ArgStr: SpaceStr, ArgLoc: SpaceLoc, Tok, ArgExprs, P&: *this, |
185 | Ctx&: Actions.Context, PP); |
186 | } |
187 | if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) { |
188 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
189 | return; |
190 | } |
191 | } break; |
192 | case ParsedAttr::AT_HLSLPackOffset: { |
193 | // Parse 'packoffset( c[Subcomponent][.component] )'. |
194 | // Check '('. |
195 | if (ExpectAndConsume(ExpectedTok: tok::l_paren, Diag: diag::err_expected_lparen_after)) { |
196 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
197 | return; |
198 | } |
199 | // Check c[Subcomponent] as an identifier. |
200 | if (!Tok.is(K: tok::identifier)) { |
201 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
202 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
203 | return; |
204 | } |
205 | StringRef OffsetStr = Tok.getIdentifierInfo()->getName(); |
206 | SourceLocation SubComponentLoc = Tok.getLocation(); |
207 | if (OffsetStr[0] != 'c') { |
208 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_hlsl_packoffset_invalid_reg) |
209 | << OffsetStr; |
210 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
211 | return; |
212 | } |
213 | OffsetStr = OffsetStr.substr(Start: 1); |
214 | unsigned SubComponent = 0; |
215 | if (!OffsetStr.empty()) { |
216 | // Make sure SubComponent is a number. |
217 | if (OffsetStr.getAsInteger(Radix: 10, Result&: SubComponent)) { |
218 | Diag(Loc: SubComponentLoc.getLocWithOffset(Offset: 1), |
219 | DiagID: diag::err_hlsl_unsupported_register_number); |
220 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
221 | return; |
222 | } |
223 | } |
224 | unsigned Component = 0; |
225 | ConsumeToken(); // consume identifier. |
226 | SourceLocation ComponentLoc; |
227 | if (Tok.is(K: tok::period)) { |
228 | ConsumeToken(); // consume period. |
229 | if (!Tok.is(K: tok::identifier)) { |
230 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
231 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
232 | return; |
233 | } |
234 | StringRef ComponentStr = Tok.getIdentifierInfo()->getName(); |
235 | ComponentLoc = Tok.getLocation(); |
236 | ConsumeToken(); // consume identifier. |
237 | // Make sure Component is a single character. |
238 | if (ComponentStr.size() != 1) { |
239 | Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component) |
240 | << ComponentStr; |
241 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
242 | return; |
243 | } |
244 | switch (ComponentStr[0]) { |
245 | case 'x': |
246 | case 'r': |
247 | Component = 0; |
248 | break; |
249 | case 'y': |
250 | case 'g': |
251 | Component = 1; |
252 | break; |
253 | case 'z': |
254 | case 'b': |
255 | Component = 2; |
256 | break; |
257 | case 'w': |
258 | case 'a': |
259 | Component = 3; |
260 | break; |
261 | default: |
262 | Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component) |
263 | << ComponentStr; |
264 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
265 | return; |
266 | } |
267 | } |
268 | ASTContext &Ctx = Actions.getASTContext(); |
269 | QualType SizeTy = Ctx.getSizeType(); |
270 | uint64_t SizeTySize = Ctx.getTypeSize(T: SizeTy); |
271 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
272 | C: Ctx, V: llvm::APInt(SizeTySize, SubComponent), type: SizeTy, l: SubComponentLoc)); |
273 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
274 | C: Ctx, V: llvm::APInt(SizeTySize, Component), type: SizeTy, l: ComponentLoc)); |
275 | if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) { |
276 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
277 | return; |
278 | } |
279 | } break; |
280 | case ParsedAttr::UnknownAttribute: |
281 | Diag(Loc, DiagID: diag::err_unknown_hlsl_semantic) << II; |
282 | return; |
283 | case ParsedAttr::AT_HLSLSV_GroupIndex: |
284 | case ParsedAttr::AT_HLSLSV_DispatchThreadID: |
285 | break; |
286 | default: |
287 | llvm_unreachable("invalid HLSL Annotation" ); |
288 | break; |
289 | } |
290 | |
291 | Attrs.addNew(attrName: II, attrRange: Loc, scopeName: nullptr, scopeLoc: SourceLocation(), args: ArgExprs.data(), |
292 | numArgs: ArgExprs.size(), form: ParsedAttr::Form::HLSLAnnotation()); |
293 | } |
294 | |