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
20using namespace clang;
21
22static 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(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
51Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd,
52 ParsedAttributes &Attrs) {
53 assert((Tok.is(tok::kw_cbuffer) || Tok.is(tok::kw_tbuffer)) &&
54 "Not a cbuffer or tbuffer!");
55 bool IsCBuffer = Tok.is(K: tok::kw_cbuffer);
56 SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'.
57
58 if (!Tok.is(K: tok::identifier)) {
59 Diag(Tok, DiagID: diag::err_expected) << tok::identifier;
60 return nullptr;
61 }
62
63 IdentifierInfo *Identifier = Tok.getIdentifierInfo();
64 SourceLocation IdentifierLoc = ConsumeToken();
65
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 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
105static 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 = new (Ctx) IdentifierLoc(ArgLoc, PP.getIdentifierInfo(Name: FixedArg));
119}
120
121Parser::ParsedSemantic Parser::ParseHLSLSemantic() {
122 assert(Tok.is(tok::identifier) && "Not a HLSL Annotation");
123
124 // Semantic pattern: [A-Za-z_]([A-Za-z_0-9]*[A-Za-z_])?[0-9]*
125 // The first part is the semantic name, the second is the optional
126 // semantic index. The semantic index is the number at the end of
127 // the semantic, including leading zeroes. Digits located before
128 // the last letter are part of the semantic name.
129 SmallString<256> Buffer;
130 Buffer.resize(N: Tok.getLength() + 1);
131 StringRef Identifier = PP.getSpelling(Tok, Buffer);
132 assert(Identifier.size() > 0);
133 // Determine the start of the semantic index.
134 unsigned IndexIndex = Identifier.find_last_not_of(Chars: "0123456789") + 1;
135
136 // ParseHLSLSemantic being called on an indentifier, the first
137 // character cannot be a digit. This error should be handled by
138 // the caller. We can assert here.
139 StringRef SemanticName = Identifier.take_front(N: IndexIndex);
140 assert(SemanticName.size() > 0);
141
142 unsigned Index = 0;
143 bool Explicit = false;
144 if (IndexIndex != Identifier.size()) {
145 Explicit = true;
146 [[maybe_unused]] bool Failure =
147 Identifier.substr(Start: IndexIndex).getAsInteger(Radix: 10, Result&: Index);
148 // Given the logic above, this should never fail.
149 assert(!Failure);
150 }
151
152 return {.Name: SemanticName, .Index: Index, .Explicit: Explicit};
153}
154
155void Parser::ParseHLSLAnnotations(ParsedAttributes &Attrs,
156 SourceLocation *EndLoc,
157 bool CouldBeBitField) {
158
159 assert(Tok.is(tok::colon) && "Not a HLSL Annotation");
160 Token OldToken = Tok;
161 ConsumeToken();
162
163 IdentifierInfo *II = nullptr;
164 if (Tok.is(K: tok::kw_register))
165 II = PP.getIdentifierInfo(Name: "register");
166 else if (Tok.is(K: tok::identifier))
167 II = Tok.getIdentifierInfo();
168
169 if (!II) {
170 if (CouldBeBitField) {
171 UnconsumeToken(Consumed&: OldToken);
172 return;
173 }
174 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected_semantic_identifier);
175 return;
176 }
177
178 ParsedAttr::Kind AttrKind =
179 ParsedAttr::getParsedKind(Name: II, Scope: nullptr, SyntaxUsed: ParsedAttr::AS_HLSLAnnotation);
180 Parser::ParsedSemantic Semantic;
181 if (AttrKind == ParsedAttr::AT_HLSLUnparsedSemantic)
182 Semantic = ParseHLSLSemantic();
183
184 SourceLocation Loc = ConsumeToken();
185 if (EndLoc)
186 *EndLoc = Tok.getLocation();
187
188 SourceLocation AttrEndLoc = Loc;
189 ArgsVector ArgExprs;
190 switch (AttrKind) {
191 case ParsedAttr::AT_HLSLResourceBinding: {
192 if (ExpectAndConsume(ExpectedTok: tok::l_paren, Diag: diag::err_expected_lparen_after)) {
193 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
194 return;
195 }
196 if (!Tok.is(K: tok::identifier)) {
197 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier;
198 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
199 return;
200 }
201 StringRef SlotStr = Tok.getIdentifierInfo()->getName();
202 SourceLocation SlotLoc = Tok.getLocation();
203 ArgExprs.push_back(Elt: ParseIdentifierLoc());
204
205 if (SlotStr.size() == 1) {
206 if (!Tok.is(K: tok::numeric_constant)) {
207 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::numeric_constant;
208 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
209 return;
210 }
211 // Add numeric_constant for fix-it.
212 fixSeparateAttrArgAndNumber(ArgStr: SlotStr, ArgLoc: SlotLoc, Tok, ArgExprs, P&: *this,
213 Ctx&: Actions.Context, PP);
214 }
215 if (Tok.is(K: tok::comma)) {
216 ConsumeToken(); // consume comma
217 if (!Tok.is(K: tok::identifier)) {
218 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier;
219 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
220 return;
221 }
222 StringRef SpaceStr = Tok.getIdentifierInfo()->getName();
223 SourceLocation SpaceLoc = Tok.getLocation();
224 ArgExprs.push_back(Elt: ParseIdentifierLoc());
225
226 // Add numeric_constant for fix-it.
227 if (SpaceStr == "space" && Tok.is(K: tok::numeric_constant))
228 fixSeparateAttrArgAndNumber(ArgStr: SpaceStr, ArgLoc: SpaceLoc, Tok, ArgExprs, P&: *this,
229 Ctx&: Actions.Context, PP);
230 }
231 AttrEndLoc = Tok.getLocation(); // location of the closing ')'
232 if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) {
233 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
234 return;
235 }
236 } break;
237 case ParsedAttr::AT_HLSLPackOffset: {
238 // Parse 'packoffset( c[Subcomponent][.component] )'.
239 // Check '('.
240 if (ExpectAndConsume(ExpectedTok: tok::l_paren, Diag: diag::err_expected_lparen_after)) {
241 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
242 return;
243 }
244 // Check c[Subcomponent] as an identifier.
245 if (!Tok.is(K: tok::identifier)) {
246 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier;
247 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
248 return;
249 }
250 StringRef OffsetStr = Tok.getIdentifierInfo()->getName();
251 SourceLocation SubComponentLoc = Tok.getLocation();
252 if (OffsetStr[0] != 'c') {
253 Diag(Loc: Tok.getLocation(), DiagID: diag::err_hlsl_packoffset_invalid_reg)
254 << OffsetStr;
255 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
256 return;
257 }
258 OffsetStr = OffsetStr.substr(Start: 1);
259 unsigned SubComponent = 0;
260 if (!OffsetStr.empty()) {
261 // Make sure SubComponent is a number.
262 if (OffsetStr.getAsInteger(Radix: 10, Result&: SubComponent)) {
263 Diag(Loc: SubComponentLoc.getLocWithOffset(Offset: 1),
264 DiagID: diag::err_hlsl_unsupported_register_number);
265 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
266 return;
267 }
268 }
269 unsigned Component = 0;
270 ConsumeToken(); // consume identifier.
271 SourceLocation ComponentLoc;
272 if (Tok.is(K: tok::period)) {
273 ConsumeToken(); // consume period.
274 if (!Tok.is(K: tok::identifier)) {
275 Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier;
276 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
277 return;
278 }
279 StringRef ComponentStr = Tok.getIdentifierInfo()->getName();
280 ComponentLoc = Tok.getLocation();
281 ConsumeToken(); // consume identifier.
282 // Make sure Component is a single character.
283 if (ComponentStr.size() != 1) {
284 Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component)
285 << ComponentStr;
286 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
287 return;
288 }
289 switch (ComponentStr[0]) {
290 case 'x':
291 case 'r':
292 Component = 0;
293 break;
294 case 'y':
295 case 'g':
296 Component = 1;
297 break;
298 case 'z':
299 case 'b':
300 Component = 2;
301 break;
302 case 'w':
303 case 'a':
304 Component = 3;
305 break;
306 default:
307 Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component)
308 << ComponentStr;
309 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
310 return;
311 }
312 }
313 ASTContext &Ctx = Actions.getASTContext();
314 QualType SizeTy = Ctx.getSizeType();
315 uint64_t SizeTySize = Ctx.getTypeSize(T: SizeTy);
316 ArgExprs.push_back(Elt: IntegerLiteral::Create(
317 C: Ctx, V: llvm::APInt(SizeTySize, SubComponent), type: SizeTy, l: SubComponentLoc));
318 ArgExprs.push_back(Elt: IntegerLiteral::Create(
319 C: Ctx, V: llvm::APInt(SizeTySize, Component), type: SizeTy, l: ComponentLoc));
320 if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) {
321 SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through )
322 return;
323 }
324 } break;
325 case ParsedAttr::AT_HLSLUnparsedSemantic: {
326 ASTContext &Ctx = Actions.getASTContext();
327 ArgExprs.push_back(Elt: IntegerLiteral::Create(
328 C: Ctx, V: llvm::APInt(Ctx.getTypeSize(T: Ctx.IntTy), Semantic.Index), type: Ctx.IntTy,
329 l: SourceLocation()));
330 ArgExprs.push_back(Elt: IntegerLiteral::Create(
331 C: Ctx, V: llvm::APInt(1, Semantic.Explicit), type: Ctx.BoolTy, l: SourceLocation()));
332 II = PP.getIdentifierInfo(Name: Semantic.Name);
333 break;
334 }
335 case ParsedAttr::UnknownAttribute: // FIXME: maybe this is obsolete?
336 break;
337 default:
338 llvm_unreachable("invalid HLSL Annotation");
339 break;
340 }
341
342 Attrs.addNew(attrName: II, attrRange: SourceRange(Loc, AttrEndLoc), scope: AttributeScopeInfo(),
343 args: ArgExprs.data(), numArgs: ArgExprs.size(),
344 form: ParsedAttr::Form::HLSLAnnotation());
345}
346