Skip to content

Commit 78939eb

Browse files
committed
[Clang][RISCV] Add support for double_check attribute
The ``clang::double_check`` attribute is used to annotate `if` statements where the condition expression should be checked twice, as a mitigation for hardware attacks. This attribute is currently RISC-V specific and only applies to `if` statements with a binary expression where the operator is one of ``==``, ``!=``, ``>``, ``>=``, ``<``, or ``<=`` and the operands are of integer type.
1 parent df140b2 commit 78939eb

File tree

12 files changed

+327
-7
lines changed

12 files changed

+327
-7
lines changed

clang/include/clang/AST/Stmt.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ class alignas(void *) Stmt {
177177
/// True if this if statement has storage for an init statement.
178178
unsigned HasInit : 1;
179179

180+
/// True if this if statement has been marked with the
181+
/// [[clang::double_check]] attributed.
182+
unsigned HasDoubleCheck : 1;
183+
180184
/// The location of the "if".
181185
SourceLocation IfLoc;
182186
};
@@ -2024,6 +2028,9 @@ class IfStmt final
20242028
/// True if this IfStmt has storage for an else statement.
20252029
bool hasElseStorage() const { return IfStmtBits.HasElse; }
20262030

2031+
bool hasDoubleCheck() const { return IfStmtBits.HasDoubleCheck; }
2032+
void setDoubleCheck() { IfStmtBits.HasDoubleCheck = true; }
2033+
20272034
Expr *getCond() {
20282035
return reinterpret_cast<Expr *>(getTrailingObjects<Stmt *>()[condOffset()]);
20292036
}

clang/include/clang/Basic/Attr.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,12 @@ def ExtVectorType : Attr {
14221422
let PragmaAttributeSupport = 0;
14231423
}
14241424

1425+
def DoubleCheck : StmtAttr, TargetSpecificAttr<TargetRISCV> {
1426+
let Spellings = [Clang<"double_check">];
1427+
let Documentation = [DoubleCheckDocs];
1428+
let Subjects = SubjectList<[IfStmt], ErrorDiag, "if statements">;
1429+
}
1430+
14251431
def FallThrough : StmtAttr {
14261432
let Spellings = [CXX11<"", "fallthrough", 201603>,
14271433
C2x<"", "fallthrough", 201910>,

clang/include/clang/Basic/AttrDocs.td

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,19 @@ use the annotated ``[[nodiscard]]`` constructor or result in an annotated type.
18881888
}];
18891889
}
18901890

1891+
def DoubleCheckDocs : Documentation {
1892+
let Category = DocCatStmt;
1893+
let Heading = "double_check";
1894+
let Content = [{
1895+
The ``clang::double_check`` attribute is used to annotate `if` statements where
1896+
the condition expression should be checked twice, as a mitigation for hardware
1897+
attacks. This attribute is currently RISC-V specific and only applies to `if`
1898+
statements with a binary expression where the operator is one of ``==``,
1899+
``!=``, ``>``, ``>=``, ``<``, or ``<=`` and the operands are of integer type.
1900+
1901+
}];
1902+
}
1903+
18911904
def FallthroughDocs : Documentation {
18921905
let Category = DocCatStmt;
18931906
let Heading = "fallthrough";

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2965,6 +2965,9 @@ def warn_function_attribute_ignored_in_stmt : Warning<
29652965
"use '%0' on statements">,
29662966
InGroup<IgnoredAttributes>;
29672967

2968+
def err_double_check_expr: Error<
2969+
"invalid expression for if statement with `double_check` attribute">;
2970+
29682971
def err_musttail_needs_trivial_args : Error<
29692972
"tail call requires that the return value, all parameters, and any "
29702973
"temporaries created by the expression are trivially destructible">;

clang/lib/AST/Stmt.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,7 @@ IfStmt::IfStmt(const ASTContext &Ctx, SourceLocation IL, IfStatementKind Kind,
929929
IfStmtBits.HasElse = HasElse;
930930
IfStmtBits.HasVar = HasVar;
931931
IfStmtBits.HasInit = HasInit;
932+
IfStmtBits.HasDoubleCheck = false;
932933

933934
setStatementKind(Kind);
934935

@@ -951,6 +952,7 @@ IfStmt::IfStmt(EmptyShell Empty, bool HasElse, bool HasVar, bool HasInit)
951952
IfStmtBits.HasElse = HasElse;
952953
IfStmtBits.HasVar = HasVar;
953954
IfStmtBits.HasInit = HasInit;
955+
IfStmtBits.HasDoubleCheck = false;
954956
}
955957

956958
IfStmt *IfStmt::Create(const ASTContext &Ctx, SourceLocation IL,

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,8 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {
830830
if (!ThenCount && !getCurrentProfileCount() &&
831831
CGM.getCodeGenOpts().OptimizationLevel)
832832
LH = Stmt::getLikelihood(S.getThen(), S.getElse());
833-
EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH);
833+
EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH,
834+
S.hasDoubleCheck());
834835

835836
// Emit the 'then' code.
836837
EmitBlock(ThenBlock);

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "llvm/IR/DataLayout.h"
4040
#include "llvm/IR/Dominators.h"
4141
#include "llvm/IR/FPEnv.h"
42+
#include "llvm/IR/InlineAsm.h"
4243
#include "llvm/IR/IntrinsicInst.h"
4344
#include "llvm/IR/Intrinsics.h"
4445
#include "llvm/IR/MDBuilder.h"
@@ -1690,19 +1691,77 @@ void CodeGenFunction::EmitBranchToCounterBlock(
16901691
EmitBranch(NextBlock);
16911692
}
16921693

1694+
static std::string DoubleCheckAsm(BinaryOperator::Opcode Op) {
1695+
std::string AsmStr;
1696+
switch (Op) {
1697+
default:
1698+
llvm_unreachable("unexpected binary operation");
1699+
case BO_EQ:
1700+
AsmStr += "bne $0, $1, $3\nbeq $0, $1, $2\n";
1701+
break;
1702+
case BO_NE:
1703+
AsmStr += "beq $0, $1, $3\nbne $0, $1, $2\n";
1704+
break;
1705+
case BO_GT:
1706+
AsmStr += "ble $0, $1, $3\nbgt $0, $1, $2\n";
1707+
break;
1708+
case BO_GE:
1709+
AsmStr += "blt $0, $1, $3\nbge $0, $1, $2\n";
1710+
break;
1711+
case BO_LT:
1712+
AsmStr += "bge $0, $1, $3\nblt $0, $1, $2\n";
1713+
break;
1714+
case BO_LE:
1715+
AsmStr += "bgt $0, $1, $3\nble $0, $1, $2\n";
1716+
break;
1717+
}
1718+
AsmStr += "unimp\nunimp";
1719+
return AsmStr;
1720+
}
1721+
16931722
/// EmitBranchOnBoolExpr - Emit a branch on a boolean condition (e.g. for an if
16941723
/// statement) to the specified blocks. Based on the condition, this might try
16951724
/// to simplify the codegen of the conditional based on the branch.
16961725
/// \param LH The value of the likelihood attribute on the True branch.
1697-
void CodeGenFunction::EmitBranchOnBoolExpr(const Expr *Cond,
1698-
llvm::BasicBlock *TrueBlock,
1699-
llvm::BasicBlock *FalseBlock,
1700-
uint64_t TrueCount,
1701-
Stmt::Likelihood LH) {
1726+
void CodeGenFunction::EmitBranchOnBoolExpr(
1727+
const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock,
1728+
uint64_t TrueCount, Stmt::Likelihood LH, bool DoubleCheck) {
17021729
Cond = Cond->IgnoreParens();
17031730

17041731
if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(Cond)) {
17051732

1733+
if (DoubleCheck) {
1734+
llvm::BasicBlock *FallthroughBlock = createBasicBlock("asm.fallthrough");
1735+
1736+
llvm::Type *ResultType = VoidTy;
1737+
llvm::Type *BinOpType = Int32Ty;
1738+
std::vector<llvm::Type *> ArgTypes;
1739+
std::vector<llvm::Value *> Args;
1740+
SmallVector<llvm::BasicBlock *, 2> Transfer;
1741+
std::string Constraints = "r,r,!i,!i";
1742+
1743+
Transfer.push_back(TrueBlock);
1744+
Transfer.push_back(FalseBlock);
1745+
1746+
llvm::Value *BinOpValue = EmitScalarExpr(CondBOp->getLHS());
1747+
Args.push_back(BinOpValue);
1748+
ArgTypes.push_back(BinOpType);
1749+
1750+
BinOpValue = EmitScalarExpr(CondBOp->getRHS());
1751+
Args.push_back(BinOpValue);
1752+
ArgTypes.push_back(BinOpType);
1753+
1754+
llvm::FunctionType *FTy =
1755+
llvm::FunctionType::get(ResultType, ArgTypes, false);
1756+
llvm::InlineAsm *IA = llvm::InlineAsm::get(
1757+
FTy, DoubleCheckAsm(CondBOp->getOpcode()), Constraints,
1758+
/*HasSideEffect*/ true, /*IsAlignStack*/ false,
1759+
/*AsmDialect*/ llvm::InlineAsm::AD_ATT, /*HasUnwindClobber*/ false);
1760+
Builder.CreateCallBr(IA, FallthroughBlock, Transfer, Args);
1761+
EmitBlock(FallthroughBlock);
1762+
return;
1763+
}
1764+
17061765
// Handle X && Y in a condition.
17071766
if (CondBOp->getOpcode() == BO_LAnd) {
17081767
// If we have "1 && X", simplify the code. "0 && X" would have constant

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4577,7 +4577,8 @@ class CodeGenFunction : public CodeGenTypeCache {
45774577
/// evaluate to true based on PGO data.
45784578
void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock,
45794579
llvm::BasicBlock *FalseBlock, uint64_t TrueCount,
4580-
Stmt::Likelihood LH = Stmt::LH_None);
4580+
Stmt::Likelihood LH = Stmt::LH_None,
4581+
bool DoubleCheck = false);
45814582

45824583
/// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is
45834584
/// nonnull, if \p LHS is marked _Nonnull.

clang/lib/Sema/SemaStmt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,10 @@ StmtResult Sema::BuildAttributedStmt(SourceLocation AttrsLoc,
590590
return SubStmt;
591591
}
592592
setFunctionHasMustTail();
593+
} else if (A->getKind() == attr::DoubleCheck) {
594+
IfStmt *IS = dyn_cast<IfStmt>(SubStmt);
595+
assert(IS && "clang::double_check attribute in unexpected statement");
596+
IS->setDoubleCheck();
593597
}
594598
}
595599

clang/lib/Sema/SemaStmtAttr.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,36 @@ static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A,
273273
return ::new (S.Context) MustTailAttr(S.Context, A);
274274
}
275275

276+
static Attr *handleDoubleCheck(Sema &S, Stmt *St, const ParsedAttr &A,
277+
SourceRange Range) {
278+
const Expr *Cond = cast<IfStmt>(St)->getCond();
279+
bool Valid = false;
280+
if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(Cond)) {
281+
BinaryOperator::Opcode Op = CondBOp->getOpcode();
282+
switch (Op) {
283+
default:
284+
break;
285+
case BO_EQ:
286+
case BO_NE:
287+
case BO_GT:
288+
case BO_GE:
289+
case BO_LT:
290+
case BO_LE: {
291+
auto LHSType = CondBOp->getRHS()->getType();
292+
auto RHSType = CondBOp->getRHS()->getType();
293+
Valid = LHSType->isIntegerType() && RHSType->isIntegerType();
294+
break;
295+
}
296+
}
297+
}
298+
if (!Valid) {
299+
S.Diag(Cond->getBeginLoc(), diag::err_double_check_expr)
300+
<< A << Cond->getSourceRange();
301+
}
302+
303+
return ::new (S.Context) DoubleCheckAttr(S.Context, A);
304+
}
305+
276306
static Attr *handleLikely(Sema &S, Stmt *St, const ParsedAttr &A,
277307
SourceRange Range) {
278308

@@ -486,6 +516,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
486516
return handleNoInlineAttr(S, St, A, Range);
487517
case ParsedAttr::AT_MustTail:
488518
return handleMustTailAttr(S, St, A, Range);
519+
case ParsedAttr::AT_DoubleCheck:
520+
return handleDoubleCheck(S, St, A, Range);
489521
case ParsedAttr::AT_Likely:
490522
return handleLikely(S, St, A, Range);
491523
case ParsedAttr::AT_Unlikely:
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// RUN: %clang_cc1 -S -emit-llvm %s -triple riscv32 -o - | FileCheck %s
2+
// RUN: %clang_cc1 -S %s -triple riscv32 -o - | FileCheck -check-prefix=CHECK-ASM %s
3+
4+
extern "C" {
5+
6+
void test_eq(int a, int b) {
7+
// CHECK: @test_eq
8+
// CHECK: callbr void asm sideeffect "bne $0, $1, $3\0Abeq $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
9+
// CHECK-NEXT: to label %asm.fallthrough [label %if.then, label %if.end]
10+
// CHECK-EMPTY:
11+
// CHECK-NEXT: asm.fallthrough: ; preds = %entry
12+
// CHECK-NEXT: br label %if.then
13+
// CHECK-EMPTY:
14+
// CHECK-NEXT: if.then: ; preds = %asm.fallthrough, %entry
15+
// CHECK-NEXT: br label %if.end
16+
// CHECK-EMPTY:
17+
// CHECK-NEXT: if.end: ; preds = %if.then, %entry
18+
// CHECK-NEXT: ret void
19+
//
20+
// CHECK-ASM: test_eq:
21+
// CHECK-ASM: #APP
22+
// CHECK-ASM-NEXT: bne a0, a1, .LBB0_3
23+
// CHECK-ASM-NEXT: beq a0, a1, .LBB0_2
24+
// CHECK-ASM-NEXT: unimp
25+
// CHECK-ASM-NEXT: unimp
26+
// CHECK-ASM-NEXT: #NO_APP
27+
// CHECK-ASM-NEXT: j .LBB0_1
28+
// CHECK-ASM-NEXT: .LBB0_1: # %asm.fallthrough
29+
// CHECK-ASM-NEXT: j .LBB0_2
30+
// CHECK-ASM-NEXT: .LBB0_2: # Block address taken
31+
// CHECK-ASM-NEXT: # %if.then
32+
// CHECK-ASM-NEXT: # Label of block must be emitted
33+
// CHECK-ASM-NEXT: j .LBB0_3
34+
// CHECK-ASM-NEXT: .LBB0_3: # Block address taken
35+
// CHECK-ASM-NEXT: # %if.end
36+
// CHECK-ASM-NEXT: # Label of block must be emitted
37+
[[clang::double_check]] if (a == b) {}
38+
}
39+
40+
void test_ne(int a, int b) {
41+
// CHECK: @test_ne
42+
// CHECK: callbr void asm sideeffect "beq $0, $1, $3\0Abne $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
43+
// CHECK-ASM: test_ne:
44+
// CHECK-ASM: beq a0, a1, .LBB1_3
45+
// CHECK-ASM-NEXT: bne a0, a1, .LBB1_2
46+
[[clang::double_check]] if (a != b) {}
47+
}
48+
49+
void test_gt(int a, int b) {
50+
// CHECK: @test_gt
51+
// CHECK: callbr void asm sideeffect "ble $0, $1, $3\0Abgt $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
52+
// CHECK-ASM: test_gt:
53+
// CHECK-ASM: bge a1, a0, .LBB2_3
54+
// CHECK-ASM-NEXT: blt a1, a0, .LBB2_2
55+
[[clang::double_check]] if (a > b) {}
56+
}
57+
58+
void test_ge(int a, int b) {
59+
// CHECK: @test_ge
60+
// CHECK: callbr void asm sideeffect "blt $0, $1, $3\0Abge $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
61+
// CHECK-ASM: test_ge:
62+
// CHECK-ASM: blt a0, a1, .LBB3_3
63+
// CHECK-ASM-NEXT: bge a0, a1, .LBB3_2
64+
[[clang::double_check]] if (a >= b) {}
65+
}
66+
67+
void test_lt(int a, int b) {
68+
// CHECK: @test_lt
69+
// CHECK: callbr void asm sideeffect "bge $0, $1, $3\0Ablt $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
70+
// CHECK-ASM: test_lt:
71+
// CHECK-ASM: bge a0, a1, .LBB4_3
72+
// CHECK-ASM-NEXT: blt a0, a1, .LBB4_2
73+
[[clang::double_check]] if (a < b) {}
74+
}
75+
76+
void test_le(int a, int b) {
77+
// CHECK: @test_le
78+
// CHECK: callbr void asm sideeffect "bgt $0, $1, $3\0Able $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
79+
// CHECK-ASM: test_le:
80+
// CHECK-ASM: blt a1, a0, .LBB5_3
81+
// CHECK-ASM-NEXT: bge a1, a0, .LBB5_2
82+
[[clang::double_check]] if (a <= b) {}
83+
}
84+
85+
// The double check applies to the outer comparison expression only.
86+
void test_sub_expr(int a, int b) {
87+
// CHECK: @test_sub_expr
88+
// CHECK: callbr void asm sideeffect "bne $0, $1, $3\0Abeq $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %mul, i32 %add)
89+
// CHECK-NOT: callbr
90+
// CHECK: ret void
91+
[[clang::double_check]] if ((a*b) == (a+b)) {}
92+
}
93+
94+
int test_else(int a, int b) {
95+
// CHECK: @test_else
96+
// CHECK: callbr void asm sideeffect "bne $0, $1, $3\0Abeq $0, $1, $2\0Aunimp\0Aunimp", "r,r,!i,!i"(i32 %0, i32 %1)
97+
// CHECK-NEXT: to label %asm.fallthrough [label %if.then, label %if.else]
98+
// CHECK-EMPTY:
99+
// CHECK-NEXT: asm.fallthrough: ; preds = %entry
100+
// CHECK-NEXT: br label %if.then
101+
// CHECK-EMPTY:
102+
// CHECK-NEXT: if.then: ; preds = %asm.fallthrough, %entry
103+
// CHECK-NEXT: store i32 1, ptr %retval, align 4
104+
// CHECK-NEXT: br label %return
105+
// CHECK-EMPTY:
106+
// CHECK-NEXT: if.else: ; preds = %entry
107+
// CHECK-NEXT: store i32 2, ptr %retval, align 4
108+
// CHECK-NEXT: br label %return
109+
// CHECK-EMPTY:
110+
// CHECK-NEXT: return: ; preds = %if.else, %if.then
111+
// CHECK-NEXT: %2 = load i32, ptr %retval, align 4
112+
// CHECK-NEXT: ret i32 %2
113+
[[clang::double_check]] if (a == b) { return 1; } else { return 2; }
114+
}
115+
116+
void test_else_unguarded_if(int a, int b) {
117+
// CHECK: @test_else_unguarded_if
118+
// CHECK: callbr void asm sideeffect
119+
// CHECK-NOT: callbr
120+
// CHECK: ret void
121+
[[clang::double_check]] if (a == b) {} else if (b == 42) {}
122+
}
123+
124+
void test_else_guarded_if(int a, int b) {
125+
// CHECK: @test_else_guarded_if
126+
// CHECK: callbr void asm sideeffect
127+
// CHECK: callbr void asm sideeffect
128+
// CHECK: ret void
129+
// CHECK-ASM: test_else_guarded_if:
130+
// CHECK-ASM: bne a0, a1, .LBB9_3
131+
// CHECK-ASM-NEXT: beq a0, a1, .LBB9_2
132+
// CHECK-ASM: li a1, 42
133+
// CHECK-ASM: bne a0, a1, .LBB9_6
134+
// CHECK-ASM-NEXT: beq a0, a1, .LBB9_5
135+
[[clang::double_check]] if (a == b) {} else [[clang::double_check]] if (b == 42) {}
136+
}
137+
138+
}

0 commit comments

Comments
 (0)