| 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(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 | 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(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 | |
| 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 | if (SlotStr.size() == 1) { |
| 167 | if (!Tok.is(K: tok::numeric_constant)) { |
| 168 | Diag(Loc: Tok.getLocation(), DiagID: 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(Loc: Tok.getLocation(), DiagID: 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(ExpectedTok: tok::r_paren, Diag: 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(ExpectedTok: tok::l_paren, Diag: 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(Loc: Tok.getLocation(), DiagID: 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(Loc: Tok.getLocation(), DiagID: 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(Loc: SubComponentLoc.getLocWithOffset(Offset: 1), |
| 224 | DiagID: 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(Loc: Tok.getLocation(), DiagID: 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(Loc: ComponentLoc, DiagID: 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(Loc: ComponentLoc, DiagID: 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(Elt: IntegerLiteral::Create( |
| 277 | C: Ctx, V: llvm::APInt(SizeTySize, SubComponent), type: SizeTy, l: SubComponentLoc)); |
| 278 | ArgExprs.push_back(Elt: IntegerLiteral::Create( |
| 279 | C: Ctx, V: llvm::APInt(SizeTySize, Component), type: SizeTy, l: ComponentLoc)); |
| 280 | if (ExpectAndConsume(ExpectedTok: tok::r_paren, Diag: 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, DiagID: 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, scope: AttributeScopeInfo(), args: ArgExprs.data(), numArgs: ArgExprs.size(), |
| 300 | form: ParsedAttr::Form::HLSLAnnotation()); |
| 301 | } |
| 302 | |