Skip to content

Commit 7ca1472

Browse files
authored
[Clang] Implement constexpr evaluation for __builtin_infer_alloc_token() (#163639)
Implement the constexpr evaluation for `__builtin_infer_alloc_token()` in Clang's constant expression evaluators (both in ExprConstant and the new bytecode interpreter). The constant evaluation is only supported for stateless (hash-based) token modes. If a stateful mode like `increment` is used, the evaluation fails, as the token value is not deterministic at compile time.
1 parent c13ac9c commit 7ca1472

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,12 @@ def note_constexpr_assumption_failed : Note<
403403
def note_constexpr_countzeroes_zero : Note<
404404
"evaluation of %select{__builtin_elementwise_clzg|__builtin_elementwise_ctzg}0 "
405405
"with a zero value is undefined">;
406+
def note_constexpr_infer_alloc_token_type_inference_failed : Note<
407+
"could not infer allocation type for __builtin_infer_alloc_token">;
408+
def note_constexpr_infer_alloc_token_no_metadata : Note<
409+
"could not get token metadata for inferred type">;
410+
def note_constexpr_infer_alloc_token_stateful_mode : Note<
411+
"stateful alloc token mode not supported in constexpr">;
406412
def err_experimental_clang_interp_failed : Error<
407413
"the experimental clang interpreter failed to evaluate an expression">;
408414

clang/lib/AST/ByteCode/InterpBuiltin.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
#include "InterpHelpers.h"
1313
#include "PrimType.h"
1414
#include "Program.h"
15+
#include "clang/AST/InferAlloc.h"
1516
#include "clang/AST/OSLog.h"
1617
#include "clang/AST/RecordLayout.h"
1718
#include "clang/Basic/Builtins.h"
1819
#include "clang/Basic/TargetBuiltins.h"
1920
#include "clang/Basic/TargetInfo.h"
2021
#include "llvm/ADT/StringExtras.h"
22+
#include "llvm/Support/AllocToken.h"
2123
#include "llvm/Support/ErrorHandling.h"
2224
#include "llvm/Support/SipHash.h"
2325

@@ -1307,6 +1309,45 @@ interp__builtin_ptrauth_string_discriminator(InterpState &S, CodePtr OpPC,
13071309
return true;
13081310
}
13091311

1312+
static bool interp__builtin_infer_alloc_token(InterpState &S, CodePtr OpPC,
1313+
const InterpFrame *Frame,
1314+
const CallExpr *Call) {
1315+
const ASTContext &ASTCtx = S.getASTContext();
1316+
uint64_t BitWidth = ASTCtx.getTypeSize(ASTCtx.getSizeType());
1317+
auto Mode =
1318+
ASTCtx.getLangOpts().AllocTokenMode.value_or(llvm::DefaultAllocTokenMode);
1319+
uint64_t MaxTokens =
1320+
ASTCtx.getLangOpts().AllocTokenMax.value_or(~0ULL >> (64 - BitWidth));
1321+
1322+
// We do not read any of the arguments; discard them.
1323+
for (int I = Call->getNumArgs() - 1; I >= 0; --I)
1324+
discard(S.Stk, *S.getContext().classify(Call->getArg(I)));
1325+
1326+
// Note: Type inference from a surrounding cast is not supported in
1327+
// constexpr evaluation.
1328+
QualType AllocType = infer_alloc::inferPossibleType(Call, ASTCtx, nullptr);
1329+
if (AllocType.isNull()) {
1330+
S.CCEDiag(Call,
1331+
diag::note_constexpr_infer_alloc_token_type_inference_failed);
1332+
return false;
1333+
}
1334+
1335+
auto ATMD = infer_alloc::getAllocTokenMetadata(AllocType, ASTCtx);
1336+
if (!ATMD) {
1337+
S.CCEDiag(Call, diag::note_constexpr_infer_alloc_token_no_metadata);
1338+
return false;
1339+
}
1340+
1341+
auto MaybeToken = llvm::getAllocToken(Mode, *ATMD, MaxTokens);
1342+
if (!MaybeToken) {
1343+
S.CCEDiag(Call, diag::note_constexpr_infer_alloc_token_stateful_mode);
1344+
return false;
1345+
}
1346+
1347+
pushInteger(S, llvm::APInt(BitWidth, *MaybeToken), ASTCtx.getSizeType());
1348+
return true;
1349+
}
1350+
13101351
static bool interp__builtin_operator_new(InterpState &S, CodePtr OpPC,
13111352
const InterpFrame *Frame,
13121353
const CallExpr *Call) {
@@ -3694,6 +3735,9 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
36943735
case Builtin::BI__builtin_ptrauth_string_discriminator:
36953736
return interp__builtin_ptrauth_string_discriminator(S, OpPC, Frame, Call);
36963737

3738+
case Builtin::BI__builtin_infer_alloc_token:
3739+
return interp__builtin_infer_alloc_token(S, OpPC, Frame, Call);
3740+
36973741
case Builtin::BI__noop:
36983742
pushInteger(S, 0, Call->getType());
36993743
return true;

clang/lib/AST/ExprConstant.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "clang/AST/CharUnits.h"
4545
#include "clang/AST/CurrentSourceLocExprScope.h"
4646
#include "clang/AST/Expr.h"
47+
#include "clang/AST/InferAlloc.h"
4748
#include "clang/AST/OSLog.h"
4849
#include "clang/AST/OptionalDiagnostic.h"
4950
#include "clang/AST/RecordLayout.h"
@@ -14663,6 +14664,27 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
1466314664
return Success(Result, E);
1466414665
}
1466514666

14667+
case Builtin::BI__builtin_infer_alloc_token: {
14668+
// If we fail to infer a type, this fails to be a constant expression; this
14669+
// can be checked with __builtin_constant_p(...).
14670+
QualType AllocType = infer_alloc::inferPossibleType(E, Info.Ctx, nullptr);
14671+
if (AllocType.isNull())
14672+
return Error(
14673+
E, diag::note_constexpr_infer_alloc_token_type_inference_failed);
14674+
auto ATMD = infer_alloc::getAllocTokenMetadata(AllocType, Info.Ctx);
14675+
if (!ATMD)
14676+
return Error(E, diag::note_constexpr_infer_alloc_token_no_metadata);
14677+
auto Mode =
14678+
Info.getLangOpts().AllocTokenMode.value_or(llvm::DefaultAllocTokenMode);
14679+
uint64_t BitWidth = Info.Ctx.getTypeSize(Info.Ctx.getSizeType());
14680+
uint64_t MaxTokens =
14681+
Info.getLangOpts().AllocTokenMax.value_or(~0ULL >> (64 - BitWidth));
14682+
auto MaybeToken = llvm::getAllocToken(Mode, *ATMD, MaxTokens);
14683+
if (!MaybeToken)
14684+
return Error(E, diag::note_constexpr_infer_alloc_token_stateful_mode);
14685+
return Success(llvm::APInt(BitWidth, *MaybeToken), E);
14686+
}
14687+
1466614688
case Builtin::BI__builtin_ffs:
1466714689
case Builtin::BI__builtin_ffsl:
1466814690
case Builtin::BI__builtin_ffsll: {

clang/test/SemaCXX/alloc-token.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,65 @@
11
// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s
22
// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -fexperimental-new-constant-interpreter
33
// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-mode=typehash -DMODE_TYPEHASH
4+
// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-max=2 -DTOKEN_MAX=2
45

56
#if !__has_builtin(__builtin_infer_alloc_token)
67
#error "missing __builtin_infer_alloc_token"
78
#endif
89

10+
struct NoPtr {
11+
int x;
12+
long y;
13+
};
14+
15+
struct WithPtr {
16+
int a;
17+
char *buf;
18+
};
19+
20+
// Check specific known values; these are guaranteed to be stable.
21+
#ifdef MODE_TYPEHASH
22+
static_assert(__builtin_infer_alloc_token(sizeof(int)) == 2689373973731826898ULL);
23+
static_assert(__builtin_infer_alloc_token(sizeof(char*)) == 2250492667400517147ULL);
24+
static_assert(__builtin_infer_alloc_token(sizeof(NoPtr)) == 7465259095297095368ULL);
25+
static_assert(__builtin_infer_alloc_token(sizeof(WithPtr)) == 11898882936532569145ULL);
26+
#elif defined(TOKEN_MAX)
27+
# if TOKEN_MAX == 2
28+
static_assert(__builtin_infer_alloc_token(sizeof(int)) == 0);
29+
static_assert(__builtin_infer_alloc_token(sizeof(char*)) == 1);
30+
static_assert(__builtin_infer_alloc_token(sizeof(NoPtr)) == 0);
31+
static_assert(__builtin_infer_alloc_token(sizeof(WithPtr)) == 1);
32+
# else
33+
# error "unhandled TOKEN_MAX case"
34+
# endif
35+
#else
36+
static_assert(__builtin_infer_alloc_token(sizeof(int)) == 2689373973731826898ULL);
37+
static_assert(__builtin_infer_alloc_token(sizeof(char*)) == 11473864704255292954ULL);
38+
static_assert(__builtin_infer_alloc_token(sizeof(NoPtr)) == 7465259095297095368ULL);
39+
static_assert(__builtin_infer_alloc_token(sizeof(WithPtr)) == 11898882936532569145ULL);
40+
#endif
41+
42+
// Template function.
43+
template <typename T>
44+
constexpr unsigned long get_token() {
45+
return __builtin_infer_alloc_token(sizeof(T));
46+
}
47+
static_assert(__builtin_infer_alloc_token(sizeof(int)) == get_token<int>());
48+
49+
// Test complex expressions.
50+
static_assert(__builtin_constant_p(__builtin_infer_alloc_token(sizeof(int))));
51+
static_assert(__builtin_infer_alloc_token(sizeof(NoPtr) * 2, 1) == get_token<NoPtr>());
52+
static_assert(__builtin_infer_alloc_token(1, 4 + sizeof(NoPtr)) == get_token<NoPtr>());
53+
static_assert(__builtin_infer_alloc_token(sizeof(NoPtr) << 8) == get_token<NoPtr>());
54+
55+
// Test usable as a template param.
56+
template <unsigned long ID, typename T>
57+
struct token_for_type {
58+
static_assert(ID == get_token<T>());
59+
static constexpr unsigned long value = ID;
60+
};
61+
static_assert(token_for_type<__builtin_infer_alloc_token(sizeof(int)), int>::value == get_token<int>());
62+
963
template <typename T = void>
1064
void template_test() {
1165
__builtin_infer_alloc_token(T()); // no error if not instantiated
@@ -20,4 +74,6 @@ void negative_tests() {
2074
__builtin_infer_alloc_token(); // expected-error {{too few arguments to function call}}
2175
__builtin_infer_alloc_token((void)0); // expected-error {{argument may not have 'void' type}}
2276
negative_template_test<void>(); // expected-note {{in instantiation of function template specialization 'negative_template_test<void>' requested here}}
77+
constexpr auto inference_fail = __builtin_infer_alloc_token(123); // expected-error {{must be initialized by a constant expression}} \
78+
// expected-note {{could not infer allocation type for __builtin_infer_alloc_token}}
2379
}

0 commit comments

Comments
 (0)