llvm-project/clang/lib/Parse/ParseHLSL.cpp
Joshua Batista c50ef30cce
[ParserHLSL] Attempt to parse HLSL annotations on Field Decls. (#96346)
`MaybeParseHLSLAnnotations` should be run on Field Decls instead of just
assuming that any colon after a field decl is a bitfield. In the case
that HLSL is the language, the code after the colon may be an
annotation. This PR gives the parser a chance to parse the subsequent
text as if it was an HLSL annotation.

The burden of parsing is now on the HLSL parser, but the actual work
needs to be done in handling every case of an hlsl annotation on a field
decl. SV_DispatchThreadID was straightforward enough to implement in
this PR, and tests have been added that the annotation appears as an
attribute in the AST.

Previously, the `hlsl_annotations_on_struct_members.hlsl` test would
result in an error shown below on the line that declares variable `a` in
struct Eg9:
error: use of undeclared identifier
     'SV_DispatchThreadID'
This is because the annotation is parsed as if it was a c++ bit field,
and an identifier
that represents an integer is expected, but not found.

This test ensures that hlsl annotations are parsed when parsing struct
decls.
This test not only ensures we make progress by moving the validation
error from the realm of
C++ and expecting bitfields, to HLSL and a specialized error for the
recognized annotation, but also
validates that the parser does parse the annotation and adds an
attribute to the field decl in the AST.

Fixes https://github.com/llvm/llvm-project/issues/57889
2024-06-28 01:53:54 -07:00

294 lines
10 KiB
C++

//===--- ParseHLSL.cpp - HLSL-specific parsing support --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the parsing logic for HLSL language features.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/Attr.h"
#include "clang/Basic/AttributeCommonInfo.h"
#include "clang/Parse/ParseDiagnostic.h"
#include "clang/Parse/Parser.h"
#include "clang/Parse/RAIIObjectsForParser.h"
#include "clang/Sema/SemaHLSL.h"
using namespace clang;
static bool validateDeclsInsideHLSLBuffer(Parser::DeclGroupPtrTy DG,
SourceLocation BufferLoc,
bool IsCBuffer, Parser &P) {
// The parse is failed, just return false.
if (!DG)
return false;
DeclGroupRef Decls = DG.get();
bool IsValid = true;
// Only allow function, variable, record decls inside HLSLBuffer.
for (DeclGroupRef::iterator I = Decls.begin(), E = Decls.end(); I != E; ++I) {
Decl *D = *I;
if (isa<CXXRecordDecl, RecordDecl, FunctionDecl, VarDecl>(D))
continue;
// FIXME: support nested HLSLBuffer and namespace inside HLSLBuffer.
if (isa<HLSLBufferDecl, NamespaceDecl>(D)) {
P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer)
<< IsCBuffer;
IsValid = false;
continue;
}
IsValid = false;
P.Diag(D->getLocation(), diag::err_invalid_declaration_in_hlsl_buffer)
<< IsCBuffer;
}
return IsValid;
}
Decl *Parser::ParseHLSLBuffer(SourceLocation &DeclEnd) {
assert((Tok.is(tok::kw_cbuffer) || Tok.is(tok::kw_tbuffer)) &&
"Not a cbuffer or tbuffer!");
bool IsCBuffer = Tok.is(tok::kw_cbuffer);
SourceLocation BufferLoc = ConsumeToken(); // Eat the 'cbuffer' or 'tbuffer'.
if (!Tok.is(tok::identifier)) {
Diag(Tok, diag::err_expected) << tok::identifier;
return nullptr;
}
IdentifierInfo *Identifier = Tok.getIdentifierInfo();
SourceLocation IdentifierLoc = ConsumeToken();
ParsedAttributes Attrs(AttrFactory);
MaybeParseHLSLAnnotations(Attrs, nullptr);
ParseScope BufferScope(this, Scope::DeclScope);
BalancedDelimiterTracker T(*this, tok::l_brace);
if (T.consumeOpen()) {
Diag(Tok, diag::err_expected) << tok::l_brace;
return nullptr;
}
Decl *D = Actions.HLSL().ActOnStartBuffer(getCurScope(), IsCBuffer, BufferLoc,
Identifier, IdentifierLoc,
T.getOpenLocation());
while (Tok.isNot(tok::r_brace) && Tok.isNot(tok::eof)) {
// FIXME: support attribute on constants inside cbuffer/tbuffer.
ParsedAttributes DeclAttrs(AttrFactory);
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
DeclGroupPtrTy Result =
ParseExternalDeclaration(DeclAttrs, EmptyDeclSpecAttrs);
if (!validateDeclsInsideHLSLBuffer(Result, IdentifierLoc, IsCBuffer,
*this)) {
T.skipToEnd();
DeclEnd = T.getCloseLocation();
BufferScope.Exit();
Actions.HLSL().ActOnFinishBuffer(D, DeclEnd);
return nullptr;
}
}
T.consumeClose();
DeclEnd = T.getCloseLocation();
BufferScope.Exit();
Actions.HLSL().ActOnFinishBuffer(D, DeclEnd);
Actions.ProcessDeclAttributeList(Actions.CurScope, D, Attrs);
return D;
}
static void fixSeparateAttrArgAndNumber(StringRef ArgStr, SourceLocation ArgLoc,
Token Tok, ArgsVector &ArgExprs,
Parser &P, ASTContext &Ctx,
Preprocessor &PP) {
StringRef Num = StringRef(Tok.getLiteralData(), Tok.getLength());
SourceLocation EndNumLoc = Tok.getEndLoc();
P.ConsumeToken(); // consume constant.
std::string FixedArg = ArgStr.str() + Num.str();
P.Diag(ArgLoc, diag::err_hlsl_separate_attr_arg_and_number)
<< FixedArg
<< FixItHint::CreateReplacement(SourceRange(ArgLoc, EndNumLoc), FixedArg);
ArgsUnion &Slot = ArgExprs.back();
Slot = IdentifierLoc::create(Ctx, ArgLoc, PP.getIdentifierInfo(FixedArg));
}
void Parser::ParseHLSLAnnotations(ParsedAttributes &Attrs,
SourceLocation *EndLoc,
bool CouldBeBitField) {
assert(Tok.is(tok::colon) && "Not a HLSL Annotation");
Token OldToken = Tok;
ConsumeToken();
IdentifierInfo *II = nullptr;
if (Tok.is(tok::kw_register))
II = PP.getIdentifierInfo("register");
else if (Tok.is(tok::identifier))
II = Tok.getIdentifierInfo();
if (!II) {
if (CouldBeBitField) {
UnconsumeToken(OldToken);
return;
}
Diag(Tok.getLocation(), diag::err_expected_semantic_identifier);
return;
}
SourceLocation Loc = ConsumeToken();
if (EndLoc)
*EndLoc = Tok.getLocation();
ParsedAttr::Kind AttrKind =
ParsedAttr::getParsedKind(II, nullptr, ParsedAttr::AS_HLSLAnnotation);
ArgsVector ArgExprs;
switch (AttrKind) {
case ParsedAttr::AT_HLSLResourceBinding: {
if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
StringRef SlotStr = Tok.getIdentifierInfo()->getName();
SourceLocation SlotLoc = Tok.getLocation();
ArgExprs.push_back(ParseIdentifierLoc());
// Add numeric_constant for fix-it.
if (SlotStr.size() == 1 && Tok.is(tok::numeric_constant))
fixSeparateAttrArgAndNumber(SlotStr, SlotLoc, Tok, ArgExprs, *this,
Actions.Context, PP);
if (Tok.is(tok::comma)) {
ConsumeToken(); // consume comma
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
StringRef SpaceStr = Tok.getIdentifierInfo()->getName();
SourceLocation SpaceLoc = Tok.getLocation();
ArgExprs.push_back(ParseIdentifierLoc());
// Add numeric_constant for fix-it.
if (SpaceStr == "space" && Tok.is(tok::numeric_constant))
fixSeparateAttrArgAndNumber(SpaceStr, SpaceLoc, Tok, ArgExprs, *this,
Actions.Context, PP);
}
if (ExpectAndConsume(tok::r_paren, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
} break;
case ParsedAttr::AT_HLSLPackOffset: {
// Parse 'packoffset( c[Subcomponent][.component] )'.
// Check '('.
if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
// Check c[Subcomponent] as an identifier.
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
StringRef OffsetStr = Tok.getIdentifierInfo()->getName();
SourceLocation SubComponentLoc = Tok.getLocation();
if (OffsetStr[0] != 'c') {
Diag(Tok.getLocation(), diag::err_hlsl_packoffset_invalid_reg)
<< OffsetStr;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
OffsetStr = OffsetStr.substr(1);
unsigned SubComponent = 0;
if (!OffsetStr.empty()) {
// Make sure SubComponent is a number.
if (OffsetStr.getAsInteger(10, SubComponent)) {
Diag(SubComponentLoc.getLocWithOffset(1),
diag::err_hlsl_unsupported_register_number);
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
}
unsigned Component = 0;
ConsumeToken(); // consume identifier.
SourceLocation ComponentLoc;
if (Tok.is(tok::period)) {
ConsumeToken(); // consume period.
if (!Tok.is(tok::identifier)) {
Diag(Tok.getLocation(), diag::err_expected) << tok::identifier;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
StringRef ComponentStr = Tok.getIdentifierInfo()->getName();
ComponentLoc = Tok.getLocation();
ConsumeToken(); // consume identifier.
// Make sure Component is a single character.
if (ComponentStr.size() != 1) {
Diag(ComponentLoc, diag::err_hlsl_unsupported_component)
<< ComponentStr;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
switch (ComponentStr[0]) {
case 'x':
case 'r':
Component = 0;
break;
case 'y':
case 'g':
Component = 1;
break;
case 'z':
case 'b':
Component = 2;
break;
case 'w':
case 'a':
Component = 3;
break;
default:
Diag(ComponentLoc, diag::err_hlsl_unsupported_component)
<< ComponentStr;
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
}
ASTContext &Ctx = Actions.getASTContext();
QualType SizeTy = Ctx.getSizeType();
uint64_t SizeTySize = Ctx.getTypeSize(SizeTy);
ArgExprs.push_back(IntegerLiteral::Create(
Ctx, llvm::APInt(SizeTySize, SubComponent), SizeTy, SubComponentLoc));
ArgExprs.push_back(IntegerLiteral::Create(
Ctx, llvm::APInt(SizeTySize, Component), SizeTy, ComponentLoc));
if (ExpectAndConsume(tok::r_paren, diag::err_expected)) {
SkipUntil(tok::r_paren, StopAtSemi); // skip through )
return;
}
} break;
case ParsedAttr::UnknownAttribute:
Diag(Loc, diag::err_unknown_hlsl_semantic) << II;
return;
case ParsedAttr::AT_HLSLSV_GroupIndex:
case ParsedAttr::AT_HLSLSV_DispatchThreadID:
break;
default:
llvm_unreachable("invalid HLSL Annotation");
break;
}
Attrs.addNew(II, Loc, nullptr, SourceLocation(), ArgExprs.data(),
ArgExprs.size(), ParsedAttr::Form::HLSLAnnotation());
}