diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index d2d9dd24536cb..6d80f0a0a6586 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4172,6 +4172,18 @@ def LoopHint : Attr { let HasCustomParsing = 1; } +/// The HLSL loop attributes +def HLSLLoopHint: StmtAttr { + /// [unroll(directive)] + /// [loop] + let Spellings = [Microsoft<"unroll">, Microsoft<"loop">]; + let Args = [UnsignedArgument<"directive", /*opt*/1>]; + let Subjects = SubjectList<[ForStmt, WhileStmt, DoStmt], + ErrorDiag, "'for', 'while', and 'do' statements">; + let LangOpts = [HLSL]; + let Documentation = [HLSLLoopHintDocs, HLSLUnrollHintDocs]; +} + def CapturedRecord : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly. let Spellings = []; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index ab4bd003541fa..09cf4f80bd999 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -7343,6 +7343,100 @@ where shaders must be compiled into a library and linked at runtime. }]; } +def HLSLLoopHintDocs : Documentation { + let Category = DocCatStmt; + let Heading = "[loop]"; + let Content = [{ +The ``[loop]`` directive allows loop optimization hints to be +specified for the subsequent loop. The directive allows unrolling to +be disabled and is not compatible with [unroll(x)]. + +Specifying the parameter, ``[loop]``, directs the +unroller to not unroll the loop. + +.. code-block:: hlsl + + [loop] + for (...) { + ... + } + +.. code-block:: hlsl + + [loop] + while (...) { + ... + } + +.. code-block:: hlsl + + [loop] + do { + ... + } while (...) + +See `hlsl loop extensions `_ +for details. + }]; +} + +def HLSLUnrollHintDocs : Documentation { + let Category = DocCatStmt; + let Heading = "[unroll(x)], [unroll]"; + let Content = [{ +Loop unrolling optimization hints can be specified with ``[unroll(x)]`` +. The attribute is placed immediately before a for, while, +or do-while. +Specifying the parameter, ``[unroll(_value_)]``, directs the +unroller to unroll the loop ``_value_`` times. Note: [unroll(x)] is not compatible with [loop]. + +.. code-block:: hlsl + + [unroll(4)] + for (...) { + ... + } + +.. code-block:: hlsl + + [unroll] + for (...) { + ... + } + +.. code-block:: hlsl + + [unroll(4)] + while (...) { + ... + } + +.. code-block:: hlsl + + [unroll] + while (...) { + ... + } + +.. code-block:: hlsl + + [unroll(4)] + do { + ... + } while (...) + +.. code-block:: hlsl + + [unroll] + do { + ... + } while (...) + +See `hlsl loop extensions `_ +for details. + }]; +} + def ClangRandomizeLayoutDocs : Documentation { let Category = DocCatDecl; let Heading = "randomize_layout, no_randomize_layout"; @@ -7402,7 +7496,8 @@ b for constant buffer views (CBV). Register space is specified in the format ``space[number]`` and defaults to ``space0`` if omitted. Here're resource binding examples with and without space: -.. code-block:: c++ + +.. code-block:: hlsl RWBuffer Uav : register(u3, space1); Buffer Buf : register(t1); @@ -7420,7 +7515,7 @@ A subcomponent is a register number, which is an integer. A component is in the Examples: -.. code-block:: c++ +.. code-block:: hlsl cbuffer A { float3 a : packoffset(c0.y); diff --git a/clang/lib/CodeGen/CGLoopInfo.cpp b/clang/lib/CodeGen/CGLoopInfo.cpp index 0d4800b90a2f2..6b886bd6b6d2c 100644 --- a/clang/lib/CodeGen/CGLoopInfo.cpp +++ b/clang/lib/CodeGen/CGLoopInfo.cpp @@ -612,9 +612,9 @@ void LoopInfoStack::push(BasicBlock *Header, clang::ASTContext &Ctx, const LoopHintAttr *LH = dyn_cast(Attr); const OpenCLUnrollHintAttr *OpenCLHint = dyn_cast(Attr); - + const HLSLLoopHintAttr *HLSLLoopHint = dyn_cast(Attr); // Skip non loop hint attributes - if (!LH && !OpenCLHint) { + if (!LH && !OpenCLHint && !HLSLLoopHint) { continue; } @@ -635,6 +635,17 @@ void LoopInfoStack::push(BasicBlock *Header, clang::ASTContext &Ctx, Option = LoopHintAttr::UnrollCount; State = LoopHintAttr::Numeric; } + } else if (HLSLLoopHint) { + ValueInt = HLSLLoopHint->getDirective(); + if (HLSLLoopHint->getSemanticSpelling() == + HLSLLoopHintAttr::Spelling::Microsoft_unroll) { + if (ValueInt == 0) + State = LoopHintAttr::Enable; + if (ValueInt > 0) { + Option = LoopHintAttr::UnrollCount; + State = LoopHintAttr::Numeric; + } + } } else if (LH) { auto *ValueExpr = LH->getValue(); if (ValueExpr) { diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 16a5b7483ec1c..22d38adc28ebe 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -114,18 +114,21 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts, // here because we don't want to allow arbitrary orderings. ParsedAttributes CXX11Attrs(AttrFactory); MaybeParseCXX11Attributes(CXX11Attrs, /*MightBeObjCMessageSend*/ true); - ParsedAttributes GNUAttrs(AttrFactory); + ParsedAttributes GNUOrMSAttrs(AttrFactory); if (getLangOpts().OpenCL) - MaybeParseGNUAttributes(GNUAttrs); + MaybeParseGNUAttributes(GNUOrMSAttrs); + + if (getLangOpts().HLSL) + MaybeParseMicrosoftAttributes(GNUOrMSAttrs); StmtResult Res = ParseStatementOrDeclarationAfterAttributes( - Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUAttrs); + Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs); MaybeDestroyTemplateIds(); // Attributes that are left should all go on the statement, so concatenate the // two lists. ParsedAttributes Attrs(AttrFactory); - takeAndConcatenateAttrs(CXX11Attrs, GNUAttrs, Attrs); + takeAndConcatenateAttrs(CXX11Attrs, GNUOrMSAttrs, Attrs); assert((Attrs.empty() || Res.isInvalid() || Res.isUsable()) && "attributes on empty statement"); diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp index 6f538ed55cb72..7f452d177c16f 100644 --- a/clang/lib/Sema/SemaStmtAttr.cpp +++ b/clang/lib/Sema/SemaStmtAttr.cpp @@ -16,6 +16,7 @@ #include "clang/Basic/TargetInfo.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/ParsedAttr.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaInternal.h" #include "llvm/ADT/StringExtras.h" @@ -584,6 +585,39 @@ static Attr *handleOpenCLUnrollHint(Sema &S, Stmt *St, const ParsedAttr &A, return ::new (S.Context) OpenCLUnrollHintAttr(S.Context, A, UnrollFactor); } +static Attr *handleHLSLLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + + if (A.getSemanticSpelling() == HLSLLoopHintAttr::Spelling::Microsoft_loop && + !A.checkAtMostNumArgs(S, 0)) + return nullptr; + + unsigned UnrollFactor = 0; + if (A.getNumArgs() == 1) { + + if (A.isArgIdent(0)) { + S.Diag(A.getLoc(), diag::err_attribute_argument_type) + << A << AANT_ArgumentIntegerConstant << A.getRange(); + return nullptr; + } + + Expr *E = A.getArgAsExpr(0); + + if (S.CheckLoopHintExpr(E, St->getBeginLoc(), + /*AllowZero=*/false)) + return nullptr; + + std::optional ArgVal = E->getIntegerConstantExpr(S.Context); + // CheckLoopHintExpr handles non int const cases + assert(ArgVal != std::nullopt && "ArgVal should be an integer constant."); + int Val = ArgVal->getSExtValue(); + // CheckLoopHintExpr handles negative and zero cases + assert(Val > 0 && "Val should be a positive integer greater than zero."); + UnrollFactor = static_cast(Val); + } + return ::new (S.Context) HLSLLoopHintAttr(S.Context, A, UnrollFactor); +} + static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { if (A.isInvalid() || A.getKind() == ParsedAttr::IgnoredAttribute) @@ -618,6 +652,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A, return handleFallThroughAttr(S, St, A, Range); case ParsedAttr::AT_LoopHint: return handleLoopHintAttr(S, St, A, Range); + case ParsedAttr::AT_HLSLLoopHint: + return handleHLSLLoopHintAttr(S, St, A, Range); case ParsedAttr::AT_OpenCLUnrollHint: return handleOpenCLUnrollHint(S, St, A, Range); case ParsedAttr::AT_Suppress: diff --git a/clang/test/CodeGenHLSL/loops/unroll.hlsl b/clang/test/CodeGenHLSL/loops/unroll.hlsl new file mode 100644 index 0000000000000..7389f21dd3472 --- /dev/null +++ b/clang/test/CodeGenHLSL/loops/unroll.hlsl @@ -0,0 +1,130 @@ +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ +// RUN: dxil-pc-shadermodel6.3-library -disable-llvm-passes %s -emit-llvm -o - | FileCheck %s + +/*** for ***/ +void for_count() +{ +// CHECK-LABEL: for_count + [unroll(8)] + for( int i = 0; i < 1000; ++i); +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_DISTINCT:.*]] +} + +void for_disable() +{ +// CHECK-LABEL: for_disable + [loop] + for( int i = 0; i < 1000; ++i); +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_DISABLE:.*]] +} + +void for_enable() +{ +// CHECK-LABEL: for_enable + [unroll] + for( int i = 0; i < 1000; ++i); +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_ENABLE:.*]] +} + +void for_nested_one_unroll_enable() +{ +// CHECK-LABEL: for_nested_one_unroll_enable + int s = 0; + [unroll] + for( int i = 0; i < 1000; ++i) { + for( int j = 0; j < 10; ++j) + s += i + j; + } +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_NESTED_ENABLE:.*]] +// CHECK-NOT: br label %{{.*}}, !llvm.loop ![[FOR_NESTED_1_ENABLE:.*]] +} + +void for_nested_two_unroll_enable() +{ +// CHECK-LABEL: for_nested_two_unroll_enable + int s = 0; + [unroll] + for( int i = 0; i < 1000; ++i) { + [unroll] + for( int j = 0; j < 10; ++j) + s += i + j; + } +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_NESTED2_ENABLE:.*]] +// CHECK: br label %{{.*}}, !llvm.loop ![[FOR_NESTED2_1_ENABLE:.*]] +} + + +/*** while ***/ +void while_count() +{ +// CHECK-LABEL: while_count + int i = 1000; + [unroll(4)] + while(i-->0); +// CHECK: br label %{{.*}}, !llvm.loop ![[WHILE_DISTINCT:.*]] +} + +void while_disable() +{ +// CHECK-LABEL: while_disable + int i = 1000; + [loop] + while(i-->0); +// CHECK: br label %{{.*}}, !llvm.loop ![[WHILE_DISABLE:.*]] +} + +void while_enable() +{ +// CHECK-LABEL: while_enable + int i = 1000; + [unroll] + while(i-->0); +// CHECK: br label %{{.*}}, !llvm.loop ![[WHILE_ENABLE:.*]] +} + +/*** do ***/ +void do_count() +{ +// CHECK-LABEL: do_count + int i = 1000; + [unroll(16)] + do {} while(i--> 0); +// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !llvm.loop ![[DO_DISTINCT:.*]] +} + +void do_disable() +{ +// CHECK-LABEL: do_disable + int i = 1000; + [loop] + do {} while(i--> 0); +// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !llvm.loop ![[DO_DISABLE:.*]] +} + +void do_enable() +{ +// CHECK-LABEL: do_enable + int i = 1000; + [unroll] + do {} while(i--> 0); +// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}, !llvm.loop ![[DO_ENABLE:.*]] +} + + +// CHECK: ![[FOR_DISTINCT]] = distinct !{![[FOR_DISTINCT]], ![[FOR_COUNT:.*]]} +// CHECK: ![[FOR_COUNT]] = !{!"llvm.loop.unroll.count", i32 8} +// CHECK: ![[FOR_DISABLE]] = distinct !{![[FOR_DISABLE]], ![[DISABLE:.*]]} +// CHECK: ![[DISABLE]] = !{!"llvm.loop.unroll.disable"} +// CHECK: ![[FOR_ENABLE]] = distinct !{![[FOR_ENABLE]], ![[ENABLE:.*]]} +// CHECK: ![[ENABLE]] = !{!"llvm.loop.unroll.enable"} +// CHECK: ![[FOR_NESTED_ENABLE]] = distinct !{![[FOR_NESTED_ENABLE]], ![[ENABLE]]} +// CHECK: ![[FOR_NESTED2_ENABLE]] = distinct !{![[FOR_NESTED2_ENABLE]], ![[ENABLE]]} +// CHECK: ![[FOR_NESTED2_1_ENABLE]] = distinct !{![[FOR_NESTED2_1_ENABLE]], ![[ENABLE]]} +// CHECK: ![[WHILE_DISTINCT]] = distinct !{![[WHILE_DISTINCT]], ![[WHILE_COUNT:.*]]} +// CHECK: ![[WHILE_COUNT]] = !{!"llvm.loop.unroll.count", i32 4} +// CHECK: ![[WHILE_DISABLE]] = distinct !{![[WHILE_DISABLE]], ![[DISABLE]]} +// CHECK: ![[WHILE_ENABLE]] = distinct !{![[WHILE_ENABLE]], ![[ENABLE]]} +// CHECK: ![[DO_DISTINCT]] = distinct !{![[DO_DISTINCT]], ![[DO_COUNT:.*]]} +// CHECK: ![[DO_COUNT]] = !{!"llvm.loop.unroll.count", i32 16} +// CHECK: ![[DO_DISABLE]] = distinct !{![[DO_DISABLE]], ![[DISABLE]]} +// CHECK: ![[DO_ENABLE]] = distinct !{![[DO_ENABLE]], ![[ENABLE]]} diff --git a/clang/test/SemaHLSL/Loops/unroll.hlsl b/clang/test/SemaHLSL/Loops/unroll.hlsl new file mode 100644 index 0000000000000..2e2be319e4666 --- /dev/null +++ b/clang/test/SemaHLSL/Loops/unroll.hlsl @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -O0 -finclude-default-header -fsyntax-only -triple dxil-pc-shadermodel6.6-library %s -verify +void unroll_no_vars() { + int I = 3; + [unroll(I)] // expected-error {{'unroll' attribute requires an integer constant}} + while (I--); +} + +void unroll_arg_count() { + [unroll(2,4)] // expected-error {{'unroll' attribute takes no more than 1 argument}} + for(int i=0; i<100; i++); +} + +void loop_arg_count() { + [loop(2)] // expected-error {{'loop' attribute takes no more than 0 argument}} + for(int i=0; i<100; i++); +} + +void unroll_no_negative() { + [unroll(-1)] // expected-error {{invalid value '-1'; must be positive}} + for(int i=0; i<100; i++); +} + +void unroll_no_zero() { + [unroll(0)] // expected-error {{invalid value '0'; must be positive}} + for(int i=0; i<100; i++); +} + +void unroll_no_float() { + [unroll(2.1)] // expected-error {{invalid argument of type 'float'; expected an integer type}} + for(int i=0; i<100; i++); +} + +void unroll_no_bool_false() { + [unroll(false)] // expected-error {{invalid argument of type 'bool'; expected an integer type}} + for(int i=0; i<100; i++); +} + +void unroll_no_bool_true() { + [unroll(true)] // expected-error {{invalid argument of type 'bool'; expected an integer type}} + for(int i=0; i<100; i++); +} + +void unroll_loop_enforcement() { + int x[10]; + [unroll(4)] // expected-error {{'unroll' attribute only applies to 'for', 'while', and 'do' statements}} + if (x[0]) + x[0] = 15; +}