Skip to content

Commit

Permalink
[clang] Add support for new loop attribute [[clang::code_align()]] (#…
Browse files Browse the repository at this point in the history
…70762)

This patch adds support for new loop attribute:
[[clang::code_align(N)]].
This attribute applies to a loop and specifies the byte alignment for a
loop.
The attribute accepts a positive integer constant initialization
expression
indicating the number of bytes for the minimum alignment boundary.
Its value must be a power of 2, between 1 and 4096 (inclusive).
  • Loading branch information
smanna12 committed Nov 20, 2023
1 parent 0e24179 commit 48ff354
Show file tree
Hide file tree
Showing 13 changed files with 511 additions and 4 deletions.
17 changes: 17 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,23 @@ Attribute Changes in Clang
should be a coroutine. A non-coroutine function marked with ``[[clang::coro_wrapper]]``
is still allowed to return the such a type. This is helpful for analyzers to recognize coroutines from the function signatures.

- Clang now supports ``[[clang::code_align(N)]]`` as an attribute which can be
applied to a loop and specifies the byte alignment for a loop. This attribute
accepts a positive integer constant initialization expression indicating the
number of bytes for the minimum alignment boundary. Its value must be a power
of 2, between 1 and 4096(inclusive).

.. code-block:: c++

void Array(int *array, size_t n) {
[[clang::code_align(64)]] for (int i = 0; i < n; ++i) array[i] = 0;
}
template<int A>
void func() {
[[clang::code_align(A)]] for(;;) { }
}

Improvements to Clang's diagnostics
-----------------------------------
- Clang constexpr evaluator now prints template arguments when displaying
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -4313,3 +4313,15 @@ def PreferredType: InheritableAttr {
let Args = [TypeArgument<"Type", 1>];
let Documentation = [PreferredTypeDocumentation];
}

def CodeAlign: StmtAttr {
let Spellings = [Clang<"code_align">];
let Subjects = SubjectList<[ForStmt, CXXForRangeStmt, WhileStmt, DoStmt],
ErrorDiag, "'for', 'while', and 'do' statements">;
let Args = [ExprArgument<"Alignment">];
let Documentation = [CodeAlignAttrDocs];
let AdditionalMembers = [{
static constexpr int MinimumAlignment = 1;
static constexpr int MaximumAlignment = 4096;
}];
}
41 changes: 41 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -7540,3 +7540,44 @@ Note: ``a_promise_type::get_return_object`` is exempted from this analysis as it
implementation detail of any coroutine library.
}];
}

def CodeAlignAttrDocs : Documentation {
let Category = DocCatVariable;
let Heading = "clang::code_align";
let Content = [{
The ``clang::code_align(N)`` attribute applies to a loop and specifies the byte
alignment for a loop. The attribute accepts a positive integer constant
initialization expression indicating the number of bytes for the minimum
alignment boundary. Its value must be a power of 2, between 1 and 4096
(inclusive).

.. code-block:: c++

void foo() {
int var = 0;
[[clang::code_align(16)]] for (int i = 0; i < 10; ++i) var++;
}

void Array(int *array, size_t n) {
[[clang::code_align(64)]] for (int i = 0; i < n; ++i) array[i] = 0;
}

void count () {
int a1[10], int i = 0;
[[clang::code_align(32)]] while (i < 10) { a1[i] += 3; }
}

void check() {
int a = 10;
[[clang::code_align(8)]] do {
a = a + 1;
} while (a < 20);
}

template<int A>
void func() {
[[clang::code_align(A)]] for(;;) { }
}

}];
}
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10025,6 +10025,11 @@ def err_duplicate_case_differing_expr : Error<
def warn_case_empty_range : Warning<"empty case range specified">;
def warn_missing_case_for_condition :
Warning<"no case matching constant switch condition '%0'">;
def err_loop_attr_conflict : Error<
"conflicting loop attribute %0">;
def err_attribute_power_of_two_in_range : Error<
"%0 attribute requires an integer argument which is a constant power of two "
"between %1 and %2 inclusive; provided argument was %3">;

def warn_def_missing_case : Warning<"%plural{"
"1:enumeration value %1 not explicitly handled in switch|"
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2099,6 +2099,9 @@ class Sema final {
QualType BuildAddressSpaceAttr(QualType &T, Expr *AddrSpace,
SourceLocation AttrLoc);

CodeAlignAttr *BuildCodeAlignAttr(const AttributeCommonInfo &CI, Expr *E);
bool CheckRebuiltCodeAlignStmtAttributes(ArrayRef<const Attr *> Attrs);

bool CheckQualifiedFunctionForTypeId(QualType T, SourceLocation Loc);

bool CheckFunctionReturnType(QualType T, SourceLocation Loc);
Expand Down
24 changes: 21 additions & 3 deletions clang/lib/CodeGen/CGLoopInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,14 @@ MDNode *LoopInfo::createMetadata(
Ctx, {MDString::get(Ctx, "llvm.loop.parallel_accesses"), AccGroup}));
}

// Setting clang::code_align attribute.
if (Attrs.CodeAlign > 0) {
Metadata *Vals[] = {MDString::get(Ctx, "llvm.loop.align"),
ConstantAsMetadata::get(ConstantInt::get(
llvm::Type::getInt32Ty(Ctx), Attrs.CodeAlign))};
LoopProperties.push_back(MDNode::get(Ctx, Vals));
}

LoopProperties.insert(LoopProperties.end(), AdditionalLoopProperties.begin(),
AdditionalLoopProperties.end());
return createFullUnrollMetadata(Attrs, LoopProperties, HasUserTransforms);
Expand All @@ -453,7 +461,7 @@ LoopAttributes::LoopAttributes(bool IsParallel)
VectorizeScalable(LoopAttributes::Unspecified), InterleaveCount(0),
UnrollCount(0), UnrollAndJamCount(0),
DistributeEnable(LoopAttributes::Unspecified), PipelineDisabled(false),
PipelineInitiationInterval(0), MustProgress(false) {}
PipelineInitiationInterval(0), CodeAlign(0), MustProgress(false) {}

void LoopAttributes::clear() {
IsParallel = false;
Expand All @@ -469,6 +477,7 @@ void LoopAttributes::clear() {
DistributeEnable = LoopAttributes::Unspecified;
PipelineDisabled = false;
PipelineInitiationInterval = 0;
CodeAlign = 0;
MustProgress = false;
}

Expand All @@ -493,8 +502,8 @@ LoopInfo::LoopInfo(BasicBlock *Header, const LoopAttributes &Attrs,
Attrs.VectorizeEnable == LoopAttributes::Unspecified &&
Attrs.UnrollEnable == LoopAttributes::Unspecified &&
Attrs.UnrollAndJamEnable == LoopAttributes::Unspecified &&
Attrs.DistributeEnable == LoopAttributes::Unspecified && !StartLoc &&
!EndLoc && !Attrs.MustProgress)
Attrs.DistributeEnable == LoopAttributes::Unspecified &&
Attrs.CodeAlign == 0 && !StartLoc && !EndLoc && !Attrs.MustProgress)
return;

TempLoopID = MDNode::getTemporary(Header->getContext(), std::nullopt);
Expand Down Expand Up @@ -788,6 +797,15 @@ void LoopInfoStack::push(BasicBlock *Header, clang::ASTContext &Ctx,
}
}

// Identify loop attribute 'code_align' from Attrs.
// For attribute code_align:
// n - 'llvm.loop.align i32 n' metadata will be emitted.
if (const auto *CodeAlign = getSpecificAttr<const CodeAlignAttr>(Attrs)) {
const auto *CE = cast<ConstantExpr>(CodeAlign->getAlignment());
llvm::APSInt ArgVal = CE->getResultAsAPSInt();
setCodeAlign(ArgVal.getSExtValue());
}

setMustProgress(MustProgress);

if (CGOpts.OptimizationLevel > 0)
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CodeGen/CGLoopInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ struct LoopAttributes {
/// Value for llvm.loop.pipeline.iicount metadata.
unsigned PipelineInitiationInterval;

/// Value for 'llvm.loop.align' metadata.
unsigned CodeAlign;

/// Value for whether the loop is required to make progress.
bool MustProgress;
};
Expand Down Expand Up @@ -282,6 +285,9 @@ class LoopInfoStack {
StagedAttrs.PipelineInitiationInterval = C;
}

/// Set value of code align for the next loop pushed.
void setCodeAlign(unsigned C) { StagedAttrs.CodeAlign = C; }

/// Set no progress for the next loop pushed.
void setMustProgress(bool P) { StagedAttrs.MustProgress = P; }

Expand Down
83 changes: 83 additions & 0 deletions clang/lib/Sema/SemaStmtAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,81 @@ static Attr *handleUnlikely(Sema &S, Stmt *St, const ParsedAttr &A,
return ::new (S.Context) UnlikelyAttr(S.Context, A);
}

CodeAlignAttr *Sema::BuildCodeAlignAttr(const AttributeCommonInfo &CI,
Expr *E) {
if (!E->isValueDependent()) {
llvm::APSInt ArgVal;
ExprResult Res = VerifyIntegerConstantExpression(E, &ArgVal);
if (Res.isInvalid())
return nullptr;
E = Res.get();

// This attribute requires an integer argument which is a constant power of
// two between 1 and 4096 inclusive.
if (ArgVal < CodeAlignAttr::MinimumAlignment ||
ArgVal > CodeAlignAttr::MaximumAlignment || !ArgVal.isPowerOf2()) {
if (std::optional<int64_t> Value = ArgVal.trySExtValue())
Diag(CI.getLoc(), diag::err_attribute_power_of_two_in_range)
<< CI << CodeAlignAttr::MinimumAlignment
<< CodeAlignAttr::MaximumAlignment << Value.value();
else
Diag(CI.getLoc(), diag::err_attribute_power_of_two_in_range)
<< CI << CodeAlignAttr::MinimumAlignment
<< CodeAlignAttr::MaximumAlignment << E;
return nullptr;
}
}
return new (Context) CodeAlignAttr(Context, CI, E);
}

static Attr *handleCodeAlignAttr(Sema &S, Stmt *St, const ParsedAttr &A) {

Expr *E = A.getArgAsExpr(0);
return S.BuildCodeAlignAttr(A, E);
}

// Diagnose non-identical duplicates as a 'conflicting' loop attributes
// and suppress duplicate errors in cases where the two match for
// [[clang::code_align()]] attribute.
static void CheckForDuplicateCodeAlignAttrs(Sema &S,
ArrayRef<const Attr *> Attrs) {
auto FindFunc = [](const Attr *A) { return isa<const CodeAlignAttr>(A); };
const auto *FirstItr = std::find_if(Attrs.begin(), Attrs.end(), FindFunc);

if (FirstItr == Attrs.end()) // no attributes found
return;

const auto *LastFoundItr = FirstItr;
std::optional<llvm::APSInt> FirstValue;

const auto *CAFA =
dyn_cast<ConstantExpr>(cast<CodeAlignAttr>(*FirstItr)->getAlignment());
// Return early if first alignment expression is dependent (since we don't
// know what the effective size will be), and skip the loop entirely.
if (!CAFA)
return;

while (Attrs.end() != (LastFoundItr = std::find_if(LastFoundItr + 1,
Attrs.end(), FindFunc))) {
const auto *CASA = dyn_cast<ConstantExpr>(
cast<CodeAlignAttr>(*LastFoundItr)->getAlignment());
// If the value is dependent, we can not test anything.
if (!CASA)
return;
// Test the attribute values.
llvm::APSInt SecondValue = CASA->getResultAsAPSInt();
if (!FirstValue)
FirstValue = CAFA->getResultAsAPSInt();

if (FirstValue != SecondValue) {
S.Diag((*LastFoundItr)->getLocation(), diag::err_loop_attr_conflict)
<< *FirstItr;
S.Diag((*FirstItr)->getLocation(), diag::note_previous_attribute);
}
return;
}
}

#define WANT_STMT_MERGE_LOGIC
#include "clang/Sema/AttrParsedAttrImpl.inc"
#undef WANT_STMT_MERGE_LOGIC
Expand Down Expand Up @@ -523,6 +598,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
return handleLikely(S, St, A, Range);
case ParsedAttr::AT_Unlikely:
return handleUnlikely(S, St, A, Range);
case ParsedAttr::AT_CodeAlign:
return handleCodeAlignAttr(S, St, A);
default:
// N.B., ClangAttrEmitter.cpp emits a diagnostic helper that ensures a
// declaration attribute is not written on a statement, but this code is
Expand All @@ -541,4 +618,10 @@ void Sema::ProcessStmtAttributes(Stmt *S, const ParsedAttributes &InAttrs,
}

CheckForIncompatibleAttributes(*this, OutAttrs);
CheckForDuplicateCodeAlignAttrs(*this, OutAttrs);
}

bool Sema::CheckRebuiltCodeAlignStmtAttributes(ArrayRef<const Attr *> Attrs) {
CheckForDuplicateCodeAlignAttrs(*this, Attrs);
return false;
}
8 changes: 7 additions & 1 deletion clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1374,7 +1374,7 @@ namespace {
const AlwaysInlineAttr *
TransformStmtAlwaysInlineAttr(const Stmt *OrigS, const Stmt *InstS,
const AlwaysInlineAttr *A);

const CodeAlignAttr *TransformCodeAlignAttr(const CodeAlignAttr *CA);
ExprResult TransformPredefinedExpr(PredefinedExpr *E);
ExprResult TransformDeclRefExpr(DeclRefExpr *E);
ExprResult TransformCXXDefaultArgExpr(CXXDefaultArgExpr *E);
Expand Down Expand Up @@ -1906,6 +1906,12 @@ const AlwaysInlineAttr *TemplateInstantiator::TransformStmtAlwaysInlineAttr(
return A;
}

const CodeAlignAttr *
TemplateInstantiator::TransformCodeAlignAttr(const CodeAlignAttr *CA) {
Expr *TransformedExpr = getDerived().TransformExpr(CA->getAlignment()).get();
return getSema().BuildCodeAlignAttr(*CA, TransformedExpr);
}

ExprResult TemplateInstantiator::transformNonTypeTemplateParmRef(
Decl *AssociatedDecl, const NonTypeTemplateParmDecl *parm,
SourceLocation loc, TemplateArgument arg,
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,8 @@ class TreeTransform {
StmtResult RebuildAttributedStmt(SourceLocation AttrLoc,
ArrayRef<const Attr *> Attrs,
Stmt *SubStmt) {
if (SemaRef.CheckRebuiltCodeAlignStmtAttributes(Attrs))
return StmtError();
return SemaRef.BuildAttributedStmt(AttrLoc, Attrs, SubStmt);
}

Expand Down
59 changes: 59 additions & 0 deletions clang/test/CodeGen/code_align.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -x c %s %s -o - | FileCheck -check-prefix=CHECK-C %s
// RUN: %clang_cc1 -fsyntax-only -emit-llvm -x c++ -std=c++11 %s -o - | FileCheck %s --check-prefixes CHECK-C,CHECK-CPP

// CHECK-C: br label %for.cond, !llvm.loop ![[MD_FP:[0-9]+]]
// CHECK-C: br label %while.cond, !llvm.loop ![[MD_FP_1:[0-9]+]]
// CHECK-C: br i1 %cmp3, label %do.body, label %do.end, !llvm.loop ![[MD_FP_2:[0-9]+]]
// CHECK-C: br label %for.cond5, !llvm.loop ![[MD_FP_3:[0-9]+]]

// CHECK-CPP: br label %for.cond, !llvm.loop ![[MD_FP_4:[0-9]+]]
// CHECK-CPP: br label %for.cond2, !llvm.loop ![[MD_FP_5:[0-9]+]]

void bar(int);
void code_align() {
int a[10];
// CHECK-C: ![[MD_FP]] = distinct !{![[MD_FP]], ![[MP:[0-9]+]], ![[MD_code_align:[0-9]+]]}
// CHECK-C-NEXT: ![[MP]] = !{!"llvm.loop.mustprogress"}
// CHECK-C-NEXT: ![[MD_code_align]] = !{!"llvm.loop.align", i32 4}
[[clang::code_align(4)]]
for(int I=0; I<128; ++I) { bar(I); }

// CHECK-C: ![[MD_FP_1]] = distinct !{![[MD_FP_1]], ![[MP]], ![[MD_code_align_1:[0-9]+]]}
// CHECK-C-NEXT: ![[MD_code_align_1]] = !{!"llvm.loop.align", i32 16}
int i = 0;
[[clang::code_align(16)]] while (i < 60) {
a[i] += 3;
}

// CHECK-C: ![[MD_FP_2]] = distinct !{![[MD_FP_2]], ![[MP]], ![[MD_code_align_2:[0-9]+]]}
// CHECK-C-NEXT: ![[MD_code_align_2]] = !{!"llvm.loop.align", i32 8}
int b = 10;
[[clang::code_align(8)]] do {
b = b + 1;
} while (b < 20);

// CHECK-C: ![[MD_FP_3]] = distinct !{![[MD_FP_3]], ![[MP]], ![[MD_code_align_3:[0-9]+]]}
// CHECK-C-NEXT: ![[MD_code_align_3]] = !{!"llvm.loop.align", i32 64}
[[clang::code_align(64)]]
for(int I=0; I<128; ++I) { bar(I); }
}

#if __cplusplus >= 201103L
template <int A, int B>
void code_align_cpp() {
int a[10];
// CHECK-CPP: ![[MD_FP_4]] = distinct !{![[MD_FP_4]], ![[MP]], ![[MD_code_align_4:[0-9]+]]}
// CHECK-CPP-NEXT: ![[MD_code_align_4]] = !{!"llvm.loop.align", i32 32}
[[clang::code_align(A)]] for (int i = 0; i != 10; ++i)
a[i] = 0;

// CHECK-CPP: ![[MD_FP_5]] = distinct !{![[MD_FP_5]], ![[MD_code_align]]}
int c[] = {0, 1, 2, 3, 4, 5};
[[clang::code_align(B)]] for (int n : c) { n *= 2; }
}

int main() {
code_align_cpp<32, 4>();
return 0;
}
#endif

1 comment on commit 48ff354

@omjavaid
Copy link
Contributor

@omjavaid omjavaid commented on 48ff354 Nov 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks LLVM buildbots:
https://lab.llvm.org/buildbot/#/builders/245
https://lab.llvm.org/buildbot/#/builders/178
https://lab.llvm.org/buildbot/#/builders/182
https://lab.llvm.org/buildbot/#/builders/186
https://lab.llvm.org/buildbot/#/builders/187

Please fix test code_align.c which seems to be failing across the board on 32bit arm (see log below)

******************** TEST 'Clang :: Sema/code_align.c' FAILED ********************
Exit Code: 1
Command Output (stderr):

RUN: at line 1: /home/tcwg-buildbot/worker/clang-armv8-quick/stage1/bin/clang -cc1 -internal-isystem /home/tcwg-buildbot/worker/clang-armv8-quick/stage1/lib/clang/18/include -nostdsysteminc -fsyntax-only -verify=expected,c-local -x c /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c

  • /home/tcwg-buildbot/worker/clang-armv8-quick/stage1/bin/clang -cc1 -internal-isystem /home/tcwg-buildbot/worker/clang-armv8-quick/stage1/lib/clang/18/include -nostdsysteminc -fsyntax-only -verify=expected,c-local -x c /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c
    error: 'c-local-error' diagnostics expected but not seen:
    File /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c Line 79 (directive at /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c:78): 'code_align' attribute requires an integer argument which is a constant power of two between 1 and 4096 inclusive; provided argument was (__int128_t)1311768467294899680ULL << 64
    File /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c Line 89 (directive at /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c:88): 'code_align' attribute requires an integer argument which is a constant power of two between 1 and 4096 inclusive; provided argument was -(__int128_t)1311768467294899680ULL << 64
    error: 'c-local-error' diagnostics seen but not expected:
    File /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c Line 79: use of undeclared identifier '__int128_t'
    File /home/tcwg-buildbot/worker/clang-armv8-quick/llvm/clang/test/Sema/code_align.c Line 89: use of undeclared identifier '__int128_t'
    4 errors generated.
    --

Please sign in to comment.