| 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 | #include <limits> |
| 20 | |
| 21 | using namespace clang; |
| 22 | |
| 23 | static bool validateDeclsInsideHLSLBuffer(Parser::DeclGroupPtrTy DG, |
| 24 | SourceLocation BufferLoc, |
| 25 | bool IsCBuffer, Parser &P) { |
| 26 | // The parse is failed, just return false. |
| 27 | if (!DG) |
| 28 | return false; |
| 29 | DeclGroupRef Decls = DG.get(); |
| 30 | bool IsValid = true; |
| 31 | // Only allow function, variable, record, and empty decls inside HLSLBuffer. |
| 32 | for (DeclGroupRef::iterator I = Decls.begin(), E = Decls.end(); I != E; ++I) { |
| 33 | Decl *D = *I; |
| 34 | if (isa<CXXRecordDecl, RecordDecl, FunctionDecl, VarDecl, EmptyDecl>(Val: D)) |
| 35 | continue; |
| 36 | |
| 37 | // FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer. |
| 38 | if (isa<HLSLBufferDecl, NamespaceDecl>(Val: D)) { |
| 39 | P.Diag(Loc: D->getLocation(), DiagID: diag::err_invalid_declaration_in_hlsl_buffer) |
| 40 | << IsCBuffer; |
| 41 | IsValid = false; |
| 42 | continue; |
| 43 | } |
| 44 | |
| 45 | IsValid = false; |
| 46 | P.Diag(Loc: D->getLocation(), DiagID: diag::err_invalid_declaration_in_hlsl_buffer) |
| 47 | << IsCBuffer; |
| 48 | } |
| 49 | return IsValid; |
| 50 | } |
| 51 | |
| 52 | Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd, |
| 53 | ParsedAttributes &Attrs) { |
| 54 | assert((Tok.is(tok::kw_cbuffer) || Tok.is(tok::kw_tbuffer)) && |
| 55 | "Not a cbuffer or tbuffer!" ); |
| 56 | bool IsCBuffer = Tok.is(K: tok::kw_cbuffer); |
| 57 | SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'. |
| 58 | |
| 59 | if (!Tok.is(K: tok::identifier)) { |
| 60 | Diag(Tok, DiagID: diag::err_expected) << tok::identifier; |
| 61 | return nullptr; |
| 62 | } |
| 63 | |
| 64 | IdentifierInfo *Identifier = Tok.getIdentifierInfo(); |
| 65 | SourceLocation IdentifierLoc = ConsumeToken(); |
| 66 | |
| 67 | MaybeParseHLSLAnnotations(Attrs, EndLoc: nullptr); |
| 68 | |
| 69 | ParseScope BufferScope(this, Scope::DeclScope); |
| 70 | BalancedDelimiterTracker T(*this, tok::l_brace); |
| 71 | if (T.consumeOpen()) { |
| 72 | Diag(Tok, DiagID: diag::err_expected) << tok::l_brace; |
| 73 | return nullptr; |
| 74 | } |
| 75 | |
| 76 | Decl *D = Actions.HLSL().ActOnStartBuffer(BufferScope: getCurScope(), CBuffer: IsCBuffer, KwLoc: BufferLoc, |
| 77 | Ident: Identifier, IdentLoc: IdentifierLoc, |
| 78 | LBrace: T.getOpenLocation()); |
| 79 | Actions.ProcessDeclAttributeList(S: Actions.CurScope, D, AttrList: Attrs); |
| 80 | |
| 81 | while (Tok.isNot(K: tok::r_brace) && Tok.isNot(K: tok::eof)) { |
| 82 | // FIXME: support attribute on constants inside cbuffer/tbuffer. |
| 83 | ParsedAttributes DeclAttrs(AttrFactory); |
| 84 | ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); |
| 85 | |
| 86 | DeclGroupPtrTy Result = |
| 87 | ParseExternalDeclaration(DeclAttrs, DeclSpecAttrs&: EmptyDeclSpecAttrs); |
| 88 | if (!validateDeclsInsideHLSLBuffer(DG: Result, BufferLoc: IdentifierLoc, IsCBuffer, |
| 89 | P&: *this)) { |
| 90 | T.skipToEnd(); |
| 91 | DeclEnd = T.getCloseLocation(); |
| 92 | BufferScope.Exit(); |
| 93 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
| 94 | return nullptr; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | T.consumeClose(); |
| 99 | DeclEnd = T.getCloseLocation(); |
| 100 | BufferScope.Exit(); |
| 101 | Actions.HLSL().ActOnFinishBuffer(Dcl: D, RBrace: DeclEnd); |
| 102 | |
| 103 | return D; |
| 104 | } |
| 105 | |
| 106 | static void fixSeparateAttrArgAndNumber(StringRef ArgStr, SourceLocation ArgLoc, |
| 107 | Token Tok, ArgsVector &ArgExprs, |
| 108 | Parser &P, ASTContext &Ctx, |
| 109 | Preprocessor &PP) { |
| 110 | StringRef Num = StringRef(Tok.getLiteralData(), Tok.getLength()); |
| 111 | SourceLocation EndNumLoc = Tok.getEndLoc(); |
| 112 | |
| 113 | P.ConsumeToken(); // consume constant. |
| 114 | std::string FixedArg = ArgStr.str() + Num.str(); |
| 115 | P.Diag(Loc: ArgLoc, DiagID: diag::err_hlsl_separate_attr_arg_and_number) |
| 116 | << FixedArg |
| 117 | << FixItHint::CreateReplacement(RemoveRange: SourceRange(ArgLoc, EndNumLoc), Code: FixedArg); |
| 118 | ArgsUnion &Slot = ArgExprs.back(); |
| 119 | Slot = new (Ctx) IdentifierLoc(ArgLoc, PP.getIdentifierInfo(Name: FixedArg)); |
| 120 | } |
| 121 | |
| 122 | Parser::ParsedSemantic Parser::ParseHLSLSemantic() { |
| 123 | assert(Tok.is(tok::identifier) && "Not a HLSL Annotation" ); |
| 124 | |
| 125 | // Semantic pattern: [A-Za-z_]([A-Za-z_0-9]*[A-Za-z_])?[0-9]* |
| 126 | // The first part is the semantic name, the second is the optional |
| 127 | // semantic index. The semantic index is the number at the end of |
| 128 | // the semantic, including leading zeroes. Digits located before |
| 129 | // the last letter are part of the semantic name. |
| 130 | SmallString<256> Buffer; |
| 131 | Buffer.resize(N: Tok.getLength() + 1); |
| 132 | StringRef Identifier = PP.getSpelling(Tok, Buffer); |
| 133 | assert(Identifier.size() > 0); |
| 134 | // Determine the start of the semantic index. |
| 135 | unsigned IndexIndex = Identifier.find_last_not_of(Chars: "0123456789" ) + 1; |
| 136 | |
| 137 | // ParseHLSLSemantic being called on an indentifier, the first |
| 138 | // character cannot be a digit. This error should be handled by |
| 139 | // the caller. We can assert here. |
| 140 | StringRef SemanticName = Identifier.take_front(N: IndexIndex); |
| 141 | assert(SemanticName.size() > 0); |
| 142 | |
| 143 | uint32_t Index = 0; |
| 144 | bool Explicit = false; |
| 145 | if (IndexIndex != Identifier.size()) { |
| 146 | Explicit = true; |
| 147 | StringRef IndexStr = Identifier.substr(Start: IndexIndex); |
| 148 | if (IndexStr.getAsInteger(Radix: 10, Result&: Index)) { |
| 149 | Diag(Tok, DiagID: diag::err_hlsl_semantic_index_out_of_range) |
| 150 | << PP.getIdentifierInfo(Name: SemanticName) << IndexStr |
| 151 | << std::numeric_limits<uint32_t>::max(); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | return {.Name: SemanticName, .Index: Index, .Explicit: Explicit}; |
| 156 | } |
| 157 | |
| 158 | void Parser::ParseHLSLAnnotations(ParsedAttributes &Attrs, |
| 159 | SourceLocation *EndLoc, |
| 160 | bool CouldBeBitField) { |
| 161 | |
| 162 | assert(Tok.is(tok::colon) && "Not a HLSL Annotation" ); |
| 163 | Token OldToken = Tok; |
| 164 | ConsumeToken(); |
| 165 | |
| 166 | IdentifierInfo *II = nullptr; |
| 167 | if (Tok.is(K: tok::kw_register)) |
| 168 | II = PP.getIdentifierInfo(Name: "register" ); |
| 169 | else if (Tok.is(K: tok::identifier)) |
| 170 | II = Tok.getIdentifierInfo(); |
| 171 | |
| 172 | if (!II) { |
| 173 | if (CouldBeBitField) { |
| 174 | UnconsumeToken(Consumed&: OldToken); |
| 175 | return; |
| 176 | } |
| 177 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected_semantic_identifier); |
| 178 | return; |
| 179 | } |
| 180 | |
| 181 | ParsedAttr::Kind AttrKind = |
| 182 | ParsedAttr::getParsedKind(Name: II, Scope: nullptr, SyntaxUsed: ParsedAttr::AS_HLSLAnnotation); |
| 183 | Parser::ParsedSemantic Semantic; |
| 184 | if (AttrKind == ParsedAttr::AT_HLSLUnparsedSemantic) |
| 185 | Semantic = ParseHLSLSemantic(); |
| 186 | |
| 187 | SourceLocation Loc = ConsumeToken(); |
| 188 | if (EndLoc) |
| 189 | *EndLoc = Tok.getLocation(); |
| 190 | |
| 191 | SourceLocation AttrEndLoc = Loc; |
| 192 | ArgsVector ArgExprs; |
| 193 | switch (AttrKind) { |
| 194 | case ParsedAttr::AT_HLSLResourceBinding: { |
| 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 | if (!Tok.is(K: tok::identifier)) { |
| 200 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
| 201 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 202 | return; |
| 203 | } |
| 204 | StringRef SlotStr = Tok.getIdentifierInfo()->getName(); |
| 205 | SourceLocation SlotLoc = Tok.getLocation(); |
| 206 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
| 207 | |
| 208 | if (SlotStr.size() == 1) { |
| 209 | if (!Tok.is(K: tok::numeric_constant)) { |
| 210 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::numeric_constant; |
| 211 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 212 | return; |
| 213 | } |
| 214 | // Add numeric_constant for fix-it. |
| 215 | fixSeparateAttrArgAndNumber(ArgStr: SlotStr, ArgLoc: SlotLoc, Tok, ArgExprs, P&: *this, |
| 216 | Ctx&: Actions.Context, PP); |
| 217 | } |
| 218 | if (Tok.is(K: tok::comma)) { |
| 219 | ConsumeToken(); // consume comma |
| 220 | if (!Tok.is(K: tok::identifier)) { |
| 221 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
| 222 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 223 | return; |
| 224 | } |
| 225 | StringRef SpaceStr = Tok.getIdentifierInfo()->getName(); |
| 226 | SourceLocation SpaceLoc = Tok.getLocation(); |
| 227 | ArgExprs.push_back(Elt: ParseIdentifierLoc()); |
| 228 | |
| 229 | // Add numeric_constant for fix-it. |
| 230 | if (SpaceStr == "space" && Tok.is(K: tok::numeric_constant)) |
| 231 | fixSeparateAttrArgAndNumber(ArgStr: SpaceStr, ArgLoc: SpaceLoc, Tok, ArgExprs, P&: *this, |
| 232 | Ctx&: Actions.Context, PP); |
| 233 | } |
| 234 | AttrEndLoc = Tok.getLocation(); // location of the closing ')' |
| 235 | if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) { |
| 236 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 237 | return; |
| 238 | } |
| 239 | } break; |
| 240 | case ParsedAttr::AT_HLSLPackOffset: { |
| 241 | // Parse 'packoffset( c[Subcomponent][.component] )'. |
| 242 | // Check '('. |
| 243 | if (ExpectAndConsume(ExpectedTok: tok::l_paren, Diag: diag::err_expected_lparen_after)) { |
| 244 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 245 | return; |
| 246 | } |
| 247 | // Check c[Subcomponent] as an identifier. |
| 248 | if (!Tok.is(K: tok::identifier)) { |
| 249 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
| 250 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 251 | return; |
| 252 | } |
| 253 | StringRef OffsetStr = Tok.getIdentifierInfo()->getName(); |
| 254 | SourceLocation SubComponentLoc = Tok.getLocation(); |
| 255 | if (OffsetStr[0] != 'c') { |
| 256 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_hlsl_packoffset_invalid_reg) |
| 257 | << OffsetStr; |
| 258 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 259 | return; |
| 260 | } |
| 261 | OffsetStr = OffsetStr.substr(Start: 1); |
| 262 | unsigned SubComponent = 0; |
| 263 | if (!OffsetStr.empty()) { |
| 264 | // Make sure SubComponent is a number. |
| 265 | if (OffsetStr.getAsInteger(Radix: 10, Result&: SubComponent)) { |
| 266 | Diag(Loc: SubComponentLoc.getLocWithOffset(Offset: 1), |
| 267 | DiagID: diag::err_hlsl_unsupported_register_number); |
| 268 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 269 | return; |
| 270 | } |
| 271 | } |
| 272 | unsigned Component = 0; |
| 273 | ConsumeToken(); // consume identifier. |
| 274 | SourceLocation ComponentLoc; |
| 275 | if (Tok.is(K: tok::period)) { |
| 276 | ConsumeToken(); // consume period. |
| 277 | if (!Tok.is(K: tok::identifier)) { |
| 278 | Diag(Loc: Tok.getLocation(), DiagID: diag::err_expected) << tok::identifier; |
| 279 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 280 | return; |
| 281 | } |
| 282 | StringRef ComponentStr = Tok.getIdentifierInfo()->getName(); |
| 283 | ComponentLoc = Tok.getLocation(); |
| 284 | ConsumeToken(); // consume identifier. |
| 285 | // Make sure Component is a single character. |
| 286 | if (ComponentStr.size() != 1) { |
| 287 | Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component) |
| 288 | << ComponentStr; |
| 289 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 290 | return; |
| 291 | } |
| 292 | switch (ComponentStr[0]) { |
| 293 | case 'x': |
| 294 | case 'r': |
| 295 | Component = 0; |
| 296 | break; |
| 297 | case 'y': |
| 298 | case 'g': |
| 299 | Component = 1; |
| 300 | break; |
| 301 | case 'z': |
| 302 | case 'b': |
| 303 | Component = 2; |
| 304 | break; |
| 305 | case 'w': |
| 306 | case 'a': |
| 307 | Component = 3; |
| 308 | break; |
| 309 | default: |
| 310 | Diag(Loc: ComponentLoc, DiagID: diag::err_hlsl_unsupported_component) |
| 311 | << ComponentStr; |
| 312 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 313 | return; |
| 314 | } |
| 315 | } |
| 316 | ASTContext &Ctx = Actions.getASTContext(); |
| 317 | QualType SizeTy = Ctx.getSizeType(); |
| 318 | uint64_t SizeTySize = Ctx.getTypeSize(T: SizeTy); |
| 319 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
| 320 | C: Ctx, V: llvm::APInt(SizeTySize, SubComponent), type: SizeTy, l: SubComponentLoc)); |
| 321 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
| 322 | C: Ctx, V: llvm::APInt(SizeTySize, Component), type: SizeTy, l: ComponentLoc)); |
| 323 | if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: diag::err_expected)) { |
| 324 | SkipUntil(T: tok::r_paren, Flags: StopAtSemi); // skip through ) |
| 325 | return; |
| 326 | } |
| 327 | } break; |
| 328 | case ParsedAttr::AT_HLSLUnparsedSemantic: { |
| 329 | ASTContext &Ctx = Actions.getASTContext(); |
| 330 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
| 331 | C: Ctx, V: llvm::APInt(Ctx.getTypeSize(T: Ctx.IntTy), Semantic.Index), type: Ctx.IntTy, |
| 332 | l: SourceLocation())); |
| 333 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
| 334 | C: Ctx, V: llvm::APInt(1, Semantic.Explicit), type: Ctx.BoolTy, l: SourceLocation())); |
| 335 | II = PP.getIdentifierInfo(Name: Semantic.Name); |
| 336 | break; |
| 337 | } |
| 338 | case ParsedAttr::UnknownAttribute: // FIXME: maybe this is obsolete? |
| 339 | break; |
| 340 | default: |
| 341 | llvm_unreachable("invalid HLSL Annotation" ); |
| 342 | break; |
| 343 | } |
| 344 | |
| 345 | Attrs.addNew(attrName: II, attrRange: SourceRange(Loc, AttrEndLoc), scope: AttributeScopeInfo(), |
| 346 | args: ArgExprs.data(), numArgs: ArgExprs.size(), |
| 347 | form: ParsedAttr::Form::HLSLAnnotation()); |
| 348 | } |
| 349 | |