diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -145,6 +145,9 @@ : SubsetSubjectisExternallyVisible() && !isa(S)}], "global functions">; +def HLSLBufferObj : SubsetSubject(S)}], + "global functions">; def ClassTmpl : SubsetSubjectgetDescribedClassTemplate()}], "class templates">; @@ -4005,6 +4008,17 @@ let Documentation = [HLSLSV_GroupIndexDocs]; } +def HLSLResourceBinding: InheritableAttr { + let Spellings = [Keyword<"register">]; + let Subjects = SubjectList<[HLSLBufferObj, GlobalVar]>; + let LangOpts = [HLSL]; + let Args = [EnumArgument<"Type", "ResourceClass", + ["t", "u", "b", "s"], + ["SRV", "UAV", "CBuffer", "Sampler"] + >, DefaultIntArgument<"ID", 0>, DefaultIntArgument<"Space", 0>]; + let Documentation = [HLSLResourceBindingDocs]; +} + def HLSLShader : InheritableAttr { let Spellings = [Microsoft<"shader">]; let Subjects = SubjectList<[HLSLEntry]>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6510,6 +6510,23 @@ }]; } +def HLSLResourceBindingDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The resource binding attribute is to set the virtual registers and logical register spaces resources in HLSL are bound to. +Format is ''register(ID, space)'' like register(t3, space1). +ID must be start with +t for shader resource views (SRV), +s for samplers, +u for unordered access views (UAV), +b for constant buffer views (CBV). + +Register space is default to space0. + +The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3d12/resource-binding-in-hlsl + }]; +} + def AnnotateTypeDocs : Documentation { let Category = DocCatType; let Heading = "annotate_type"; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1604,6 +1604,11 @@ def err_expected_semantic_identifier : Error< "expected HLSL Semantic identifier">; def err_unknown_hlsl_semantic : Error<"unknown HLSL semantic %0">; +def err_hlsl_unsupported_register_type : Error<"register type is unsupported - available types are 'b', 's', 't', 'u'">; +def err_hlsl_unsupported_register_number : Error<"register number should be an integral numeric string">; +def err_hlsl_expected_space : Error<"expected space definition with syntax 'spaceX', where X is an integral value">; +def err_hlsl_unsupported_space_number : Error<"space number should be an integral numeric string">; + def ext_hlsl_access_specifiers : ExtWarn< "access specifiers are a clang HLSL extension">, InGroup; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2817,6 +2817,15 @@ void ParseHLSLSemantics(ParsedAttributes &Attrs, SourceLocation *EndLoc = nullptr); + + void MaybeParseHLSLResourceBinding(ParsedAttributes &Attrs, + SourceLocation *EndLoc = nullptr) { + if (getLangOpts().HLSL && Tok.is(tok::colon)) + ParseHLSLResourceBinding(Attrs, EndLoc); + } + void ParseHLSLResourceBinding(ParsedAttributes &Attrs, + SourceLocation *EndLoc = nullptr); + Decl *ParseHLSLBuffer(SourceLocation &DeclEnd, SourceLocation InlineLoc = SourceLocation()); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3572,6 +3572,10 @@ int X, int Y, int Z); HLSLShaderAttr *mergeHLSLShaderAttr(Decl *D, const AttributeCommonInfo &AL, HLSLShaderAttr::ShaderType ShaderType); + HLSLResourceBindingAttr * + mergeHLSLResourceBindingAttr(Decl *D, const AttributeCommonInfo &AL, + HLSLResourceBindingAttr::ResourceClass, + int RegID, int SpaceID); void mergeDeclAttributes(NamedDecl *New, Decl *Old, AvailabilityMergeKind AMK = AMK_Redeclaration); diff --git a/clang/lib/Parse/ParseHLSL.cpp b/clang/lib/Parse/ParseHLSL.cpp --- a/clang/lib/Parse/ParseHLSL.cpp +++ b/clang/lib/Parse/ParseHLSL.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/Attr.h" #include "clang/Basic/AttributeCommonInfo.h" #include "clang/Parse/ParseDiagnostic.h" #include "clang/Parse/Parser.h" @@ -32,6 +33,9 @@ IdentifierInfo *Identifier = Tok.getIdentifierInfo(); SourceLocation IdentifierLoc = ConsumeToken(); // consume identifier + ParsedAttributes Attrs(AttrFactory); + MaybeParseHLSLResourceBinding(Attrs); + ParseScope BufferScope(this, Scope::DeclScope); BalancedDelimiterTracker T(*this, tok::l_brace); if (T.consumeOpen()) { @@ -43,8 +47,6 @@ Identifier, IdentifierLoc, T.getOpenLocation()); - // FIXME: support attribute on cbuffer/tbuffer. - while (Tok.isNot(tok::r_brace) && Tok.isNot(tok::eof)) { ParsedAttributes Attrs(AttrFactory); // FIXME: support attribute on constants inside cbuffer/tbuffer. @@ -56,6 +58,7 @@ BufferScope.Exit(); Actions.ActOnFinishHLSLBuffer(D, DeclEnd); + Actions.ProcessDeclAttributeList(Actions.CurScope, D, Attrs); return D; } @@ -83,3 +86,109 @@ Attrs.addNew(II, Loc, nullptr, SourceLocation(), nullptr, 0, ParsedAttr::AS_HLSLSemantic); } + +void Parser::ParseHLSLResourceBinding(ParsedAttributes &Attrs, + SourceLocation *EndLoc) { + // : register (Type#[subcomponent] [,spaceX]) + ConsumeToken(); // consume colon. + + auto KwLoc = Tok.getLocation(); + ConsumeToken(); // consume kw_register. + + if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, + "register")) { + 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; + } + + auto ResourceLoc = Tok.getLocation(); + StringRef Text = Tok.getIdentifierInfo()->getName(); + if (Text.empty()) { + Diag(ResourceLoc, diag::err_hlsl_unsupported_register_type); + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + + // validate text. + unsigned RegID = 0; + // It's valid to omit the register number. + if (Text.size() > 1) { + StringRef NumName = Text.substr(1); + if (NumName.getAsInteger(10, RegID)) { + Diag(ResourceLoc, diag::err_hlsl_unsupported_register_number); + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + } + + StringRef ResourceType = Text.substr(0, 1); + HLSLResourceBindingAttr::ResourceClass RC; + if (!HLSLResourceBindingAttr::ConvertStrToResourceClass(ResourceType, RC)) { + Diag(ResourceLoc, diag::err_hlsl_unsupported_register_type); + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + ConsumeToken(); // consume register (type'#') + + unsigned SpaceID = 0; + 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; + } + auto SpaceLoc = Tok.getLocation(); + StringRef Text = Tok.getIdentifierInfo()->getName(); + // validate space. + if (!Text.startswith_insensitive("space")) { + Diag(SpaceLoc, diag::err_hlsl_expected_space); + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + StringRef NumName = Text.substr(5); + if (NumName.getAsInteger(10, SpaceID)) { + Diag(SpaceLoc, diag::err_hlsl_unsupported_space_number); + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + + ConsumeToken(); // consume space + } + + if (ExpectAndConsume(tok::r_paren, diag::err_expected)) { + SkipUntil(tok::r_paren, StopAtSemi); // skip through ) + return; + } + + auto &Ctx = Actions.Context; + QualType CharTy = Ctx.CharTy.withConst(); + llvm::APInt Size(Ctx.getTypeSize(Ctx.getSizeType()), 2); + QualType ArrayTy = + Ctx.getConstantArrayType(CharTy, Size, nullptr, ArrayType::Normal, 0); + + StringLiteral *SResourceType = + StringLiteral::Create(Ctx, ResourceType, StringLiteral::Ordinary, + /*Pascal*/ false, ArrayTy, ResourceLoc); + + QualType Int64Ty = Ctx.LongLongTy; + auto *CRegID = IntegerLiteral::Create(Ctx, llvm::APSInt::get(RegID), Int64Ty, + SourceLocation()); + + auto *CSpaceID = IntegerLiteral::Create(Ctx, llvm::APSInt::get(SpaceID), + Int64Ty, SourceLocation()); + + ArgsUnion Args[3] = {SResourceType, CRegID, CSpaceID}; + + Attrs.addNew(PP.getIdentifierInfo("register"), KwLoc, nullptr, + SourceLocation(), Args, 3, + ParsedAttr::AS_ContextSensitiveKeyword); + + return; +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2807,6 +2807,9 @@ S.mergeHLSLNumThreadsAttr(D, *NT, NT->getX(), NT->getY(), NT->getZ()); else if (const auto *SA = dyn_cast(Attr)) NewAttr = S.mergeHLSLShaderAttr(D, *SA, SA->getType()); + else if (const auto *RB = dyn_cast(Attr)) + NewAttr = S.mergeHLSLResourceBindingAttr(D, *RB, RB->getType(), RB->getID(), + RB->getSpace()); else if (Attr->shouldInheritEvenIfAlreadyPresent() || !DeclHasAttr(D, Attr)) NewAttr = cast(Attr->clone(S.Context)); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6931,6 +6931,63 @@ return HLSLShaderAttr::Create(Context, ShaderType, AL); } +static void handleHLSLResourceBindingAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + StringRef Str; + SourceLocation ArgLoc; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc)) + return; + + HLSLResourceBindingAttr::ResourceClass RC; + if (!HLSLResourceBindingAttr::ConvertStrToResourceClass(Str, RC)) { + S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) + << AL << Str << ArgLoc; + return; + } + if (!AL.isArgExpr(1)) { + return; + } + if (!AL.isArgExpr(2)) { + return; + } + + auto *Arg1 = AL.getArgAsExpr(1); + auto *CArg1 = dyn_cast(Arg1); + if (!CArg1) { + return; + } + auto *Arg2 = AL.getArgAsExpr(2); + auto *CArg2 = dyn_cast(Arg2); + if (!CArg2) { + return; + } + + unsigned RegID = CArg1->getValue().getLimitedValue(); + + unsigned SpaceID = CArg2->getValue().getLimitedValue(); + // FIXME: check function match the shader stage. + + HLSLResourceBindingAttr *NewAttr = + S.mergeHLSLResourceBindingAttr(D, AL, RC, RegID, SpaceID); + if (NewAttr) + D->addAttr(NewAttr); +} + +HLSLResourceBindingAttr * +Sema::mergeHLSLResourceBindingAttr(Decl *D, const AttributeCommonInfo &AL, + HLSLResourceBindingAttr::ResourceClass RC, + int RegID, int SpaceID) { + if (HLSLResourceBindingAttr *NT = D->getAttr()) { + if (NT->getType() != RC || NT->getSpace() != SpaceID || + NT->getID() != RegID) { + Diag(NT->getLocation(), diag::err_hlsl_attribute_param_mismatch) << AL; + Diag(AL.getLoc(), diag::note_conflicting_attribute); + } + return nullptr; + } + return HLSLResourceBindingAttr::Create(Context, RC, RegID, SpaceID, AL); +} + static void handleMSInheritanceAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (!S.LangOpts.CPlusPlus) { S.Diag(AL.getLoc(), diag::err_attribute_not_supported_in_lang) @@ -8907,6 +8964,9 @@ case ParsedAttr::AT_HLSLShader: handleHLSLShaderAttr(S, D, AL); break; + case ParsedAttr::AT_HLSLResourceBinding: + handleHLSLResourceBindingAttr(S, D, AL); + break; case ParsedAttr::AT_AbiTag: handleAbiTagAttr(S, D, AL); diff --git a/clang/test/AST/HLSL/resource_binding_attr.hlsl b/clang/test/AST/HLSL/resource_binding_attr.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/AST/HLSL/resource_binding_attr.hlsl @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -ast-dump -o - %s | FileCheck %s + +// CHECK:HLSLBufferDecl 0x[[CB:[0-9a-f]+]] {{.*}} line:6:9 cbuffer CB +// CHECK-NEXT:HLSLResourceBindingAttr 0x{{[0-9a-f]+}} CBuffer 3 2 +// CHECK-NEXT:VarDecl 0x[[A:[0-9a-f]+]] {{.*}} col:9 used a 'float' +cbuffer CB : register(b3, space2) { + float a; +} + +// CHECK:HLSLBufferDecl 0x[[TB:[0-9a-f]+]] {{.*}} line:13:9 tbuffer TB +// CHECK-NEXT:HLSLResourceBindingAttr 0x{{[0-9a-f]+}} SRV 2 1 +// CHECK-NEXT:VarDecl 0x[[B:[0-9a-f]+]] {{.*}} col:9 used b 'float' +tbuffer TB : register(t2, space1) { + float b; +} + +float foo() { +// CHECK: BinaryOperator 0x{{[0-9a-f]+}} 'float' '+' +// CHECK-NEXT: ImplicitCastExpr 0x{{[0-9a-f]+}} 'float' +// CHECK-NEXT: DeclRefExpr 0x{{[0-9a-f]+}} 'float' lvalue Var 0x[[A]] 'a' 'float' +// CHECK-NEXT: ImplicitCastExpr 0x{{[0-9a-f]+}} 'float' +// CHECK-NEXT: DeclRefExpr 0x{{[0-9a-f]+}} 'float' lvalue Var 0x[[B]] 'b' 'float' + return a + b; +} diff --git a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - -fsyntax-only %s -verify + +// expected-error@+5 {{expected ';' after top level declarator}} +// expected-error@+4 {{expected ')'}} +// expected-note@+3 {{to match this '('}} +// expected-error@+2 {{a type specifier is required for all declarations}} +// expected-error@+1 {{illegal storage class on file-scoped variable}} +float a : register(c0, space1); + +// expected-error@+1 {{register type is unsupported - available types are 'b', 's', 't', 'u'}} +cbuffer a : register(i0) { + +} +// expected-error@+1 {{expected space definition with syntax 'spaceX', where X is an integral value}} +cbuffer a : register(b0, s2) { + +} +// expected-error@+1 {{register number should be an integral numeric string}} +cbuffer a : register(bf, s2) { + +} +// expected-error@+1 {{space number should be an integral numeric string}} +cbuffer a : register(b2, spaces) { + +}