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>(D))
33       continue;
34 
35     // FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer.
36     if (isa<HLSLBufferDecl, NamespaceDecl>(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(tok::kw_cbuffer);
54   SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'.
55 
56   if (!Tok.is(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, 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(getCurScope(), IsCBuffer, BufferLoc,
75                                          Identifier, IdentifierLoc,
76                                          T.getOpenLocation());
77 
78   while (Tok.isNot(tok::r_brace) && Tok.isNot(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, EmptyDeclSpecAttrs);
85     if (!validateDeclsInsideHLSLBuffer(Result, IdentifierLoc, IsCBuffer,
86                                        *this)) {
87       T.skipToEnd();
88       DeclEnd = T.getCloseLocation();
89       BufferScope.Exit();
90       Actions.ActOnFinishHLSLBuffer(D, DeclEnd);
91       return nullptr;
92     }
93   }
94 
95   T.consumeClose();
96   DeclEnd = T.getCloseLocation();
97   BufferScope.Exit();
98   Actions.ActOnFinishHLSLBuffer(D, DeclEnd);
99 
100   Actions.ProcessDeclAttributeList(Actions.CurScope, D, 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, ArgLoc, PP.getIdentifierInfo(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(tok::kw_register))
130     II = PP.getIdentifierInfo("register");
131   else if (Tok.is(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(II, nullptr, 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(tok::r_paren, StopAtSemi); // skip through )
150       return;
151     }
152     if (!Tok.is(tok::identifier)) {
153       Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
154       SkipUntil(tok::r_paren, StopAtSemi); // skip through )
155       return;
156     }
157     StringRef SlotStr = Tok.getIdentifierInfo()->getName();
158     SourceLocation SlotLoc = Tok.getLocation();
159     ArgExprs.push_back(ParseIdentifierLoc());
160 
161     // Add numeric_constant for fix-it.
162     if (SlotStr.size() == 1 && Tok.is(tok::numeric_constant))
163       fixSeparateAttrArgAndNumber(SlotStr, SlotLoc, Tok, ArgExprs, *this,
164                                   Actions.Context, PP);
165 
166     if (Tok.is(tok::comma)) {
167       ConsumeToken(); // consume comma
168       if (!Tok.is(tok::identifier)) {
169         Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
170         SkipUntil(tok::r_paren, StopAtSemi); // skip through )
171         return;
172       }
173       StringRef SpaceStr = Tok.getIdentifierInfo()->getName();
174       SourceLocation SpaceLoc = Tok.getLocation();
175       ArgExprs.push_back(ParseIdentifierLoc());
176 
177       // Add numeric_constant for fix-it.
178       if (SpaceStr.equals("space") && Tok.is(tok::numeric_constant))
179         fixSeparateAttrArgAndNumber(SpaceStr, SpaceLoc, Tok, ArgExprs, *this,
180                                     Actions.Context, PP);
181     }
182     if (ExpectAndConsume(tok::r_paren, diag::err_expected)) {
183       SkipUntil(tok::r_paren, 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(II, Loc, nullptr, SourceLocation(), ArgExprs.data(),
199                ArgExprs.size(), ParsedAttr::AS_HLSLSemantic);
200 }
201